Spaces:
Runtime error
Runtime error
static_assert(sizeof(half) == sizeof(ggml_fp16_t), "wrong fp16 size"); | |
[[noreturn]] | |
void ggml_cuda_error(const char * stmt, const char * func, const char * file, int line, const char * msg) { | |
int id = -1; // in case cudaGetDevice fails | |
cudaGetDevice(&id); | |
GGML_LOG_ERROR(GGML_CUDA_NAME " error: %s\n", msg); | |
GGML_LOG_ERROR(" current device: %d, in function %s at %s:%d\n", id, func, file, line); | |
GGML_LOG_ERROR(" %s\n", stmt); | |
// abort with GGML_ABORT to get a stack trace | |
GGML_ABORT(GGML_CUDA_NAME " error"); | |
} | |
// this is faster on Windows | |
// probably because the Windows CUDA libraries forget to make this check before invoking the drivers | |
void ggml_cuda_set_device(int device) { | |
int current_device; | |
CUDA_CHECK(cudaGetDevice(¤t_device)); | |
if (device == current_device) { | |
return; | |
} | |
CUDA_CHECK(cudaSetDevice(device)); | |
} | |
int ggml_cuda_get_device() { | |
int id; | |
CUDA_CHECK(cudaGetDevice(&id)); | |
return id; | |
} | |
static cudaError_t ggml_cuda_device_malloc(void ** ptr, size_t size, int device) { | |
ggml_cuda_set_device(device); | |
#if defined(GGML_USE_HIPBLAS) && defined(GGML_HIP_UMA) | |
auto res = hipMallocManaged(ptr, size); | |
if (res == hipSuccess) { | |
// if error we "need" to know why... | |
CUDA_CHECK(hipMemAdvise(*ptr, size, hipMemAdviseSetCoarseGrain, device)); | |
} | |
return res; | |
#else | |
#if !defined(GGML_USE_HIPBLAS) | |
cudaError_t err; | |
if (getenv("GGML_CUDA_ENABLE_UNIFIED_MEMORY") != nullptr) | |
{ | |
err = cudaMallocManaged(ptr, size); | |
} | |
else | |
{ | |
err = cudaMalloc(ptr, size); | |
} | |
return err; | |
#else | |
return cudaMalloc(ptr, size); | |
#endif // !defined(GGML_USE_HIPBLAS) | |
#endif | |
} | |
static ggml_cuda_device_info ggml_cuda_init() { | |
#ifdef __HIP_PLATFORM_AMD__ | |
// Workaround for a rocBLAS bug when using multiple graphics cards: | |
// https://github.com/ROCmSoftwarePlatform/rocBLAS/issues/1346 | |
rocblas_initialize(); | |
CUDA_CHECK(cudaDeviceSynchronize()); | |
#endif | |
ggml_cuda_device_info info = {}; | |
cudaError_t err = cudaGetDeviceCount(&info.device_count); | |
if (err != cudaSuccess) { | |
GGML_LOG_ERROR("%s: failed to initialize " GGML_CUDA_NAME ": %s\n", __func__, cudaGetErrorString(err)); | |
return info; | |
} | |
GGML_ASSERT(info.device_count <= GGML_CUDA_MAX_DEVICES); | |
int64_t total_vram = 0; | |
#ifdef GGML_CUDA_FORCE_MMQ | |
GGML_LOG_INFO("%s: GGML_CUDA_FORCE_MMQ: yes\n", __func__); | |
#else | |
GGML_LOG_INFO("%s: GGML_CUDA_FORCE_MMQ: no\n", __func__); | |
#endif // GGML_CUDA_FORCE_MMQ | |
#ifdef GGML_CUDA_FORCE_CUBLAS | |
GGML_LOG_INFO("%s: GGML_CUDA_FORCE_CUBLAS: yes\n", __func__); | |
#else | |
GGML_LOG_INFO("%s: GGML_CUDA_FORCE_CUBLAS: no\n", __func__); | |
#endif // GGML_CUDA_FORCE_CUBLAS | |
GGML_LOG_INFO("%s: found %d " GGML_CUDA_NAME " devices:\n", __func__, info.device_count); | |
for (int id = 0; id < info.device_count; ++id) { | |
int device_vmm = 0; | |
#if !defined(GGML_USE_HIPBLAS) && !defined(GGML_CUDA_NO_VMM) | |
CUdevice device; | |
CU_CHECK(cuDeviceGet(&device, id)); | |
CU_CHECK(cuDeviceGetAttribute(&device_vmm, CU_DEVICE_ATTRIBUTE_VIRTUAL_MEMORY_MANAGEMENT_SUPPORTED, device)); | |
if (device_vmm) { | |
CUmemAllocationProp alloc_prop = {}; | |
alloc_prop.type = CU_MEM_ALLOCATION_TYPE_PINNED; | |
alloc_prop.location.type = CU_MEM_LOCATION_TYPE_DEVICE; | |
alloc_prop.location.id = id; | |
CU_CHECK(cuMemGetAllocationGranularity(&info.devices[id].vmm_granularity, &alloc_prop, CU_MEM_ALLOC_GRANULARITY_RECOMMENDED)); | |
} | |
#endif // !defined(GGML_USE_HIPBLAS) && !defined(GGML_CUDA_NO_VMM) | |
info.devices[id].vmm = !!device_vmm; | |
cudaDeviceProp prop; | |
CUDA_CHECK(cudaGetDeviceProperties(&prop, id)); | |
GGML_LOG_INFO(" Device %d: %s, compute capability %d.%d, VMM: %s\n", id, prop.name, prop.major, prop.minor, device_vmm ? "yes" : "no"); | |
info.default_tensor_split[id] = total_vram; | |
total_vram += prop.totalGlobalMem; | |
info.devices[id].nsm = prop.multiProcessorCount; | |
info.devices[id].smpb = prop.sharedMemPerBlock; | |
#if defined(GGML_USE_HIPBLAS) && defined(__HIP_PLATFORM_AMD__) | |
info.devices[id].smpbo = prop.sharedMemPerBlock; | |
info.devices[id].cc = 100*prop.major + 10*prop.minor + CC_OFFSET_AMD; | |
#else | |
info.devices[id].smpbo = prop.sharedMemPerBlockOptin; | |
info.devices[id].cc = 100*prop.major + 10*prop.minor; | |
#endif // defined(GGML_USE_HIPBLAS) && defined(__HIP_PLATFORM_AMD__) | |
} | |
for (int id = 0; id < info.device_count; ++id) { | |
info.default_tensor_split[id] /= total_vram; | |
} | |
// configure logging to stdout | |
// CUBLAS_CHECK(cublasLoggerConfigure(1, 1, 0, nullptr)); | |
return info; | |
} | |
const ggml_cuda_device_info & ggml_cuda_info() { | |
static ggml_cuda_device_info info = ggml_cuda_init(); | |
return info; | |
} | |
// #define DEBUG_CUDA_MALLOC | |
// buffer pool for cuda (legacy) | |
struct ggml_cuda_pool_leg : public ggml_cuda_pool { | |
static const int MAX_BUFFERS = 256; | |
int device; | |
struct ggml_cuda_buffer { | |
void * ptr = nullptr; | |
size_t size = 0; | |
}; | |
ggml_cuda_buffer buffer_pool[MAX_BUFFERS] = {}; | |
size_t pool_size = 0; | |
explicit ggml_cuda_pool_leg(int device) : | |
device(device) { | |
} | |
~ggml_cuda_pool_leg() { | |
ggml_cuda_set_device(device); | |
for (int i = 0; i < MAX_BUFFERS; ++i) { | |
ggml_cuda_buffer & b = buffer_pool[i]; | |
if (b.ptr != nullptr) { | |
CUDA_CHECK(cudaFree(b.ptr)); | |
pool_size -= b.size; | |
} | |
} | |
GGML_ASSERT(pool_size == 0); | |
} | |
void * alloc(size_t size, size_t * actual_size) override { | |
#ifdef DEBUG_CUDA_MALLOC | |
int nnz = 0; | |
size_t max_size = 0; | |
#endif | |
size_t best_diff = 1ull << 36; | |
int ibest = -1; | |
for (int i = 0; i < MAX_BUFFERS; ++i) { | |
ggml_cuda_buffer& b = buffer_pool[i]; | |
if (b.ptr != nullptr) { | |
#ifdef DEBUG_CUDA_MALLOC | |
++nnz; | |
if (b.size > max_size) max_size = b.size; | |
#endif | |
if (b.size >= size) { | |
size_t diff = b.size - size; | |
if (diff < best_diff) { | |
best_diff = diff; | |
ibest = i; | |
if (!best_diff) { | |
void * ptr = b.ptr; | |
*actual_size = b.size; | |
b.ptr = nullptr; | |
b.size = 0; | |
return ptr; | |
} | |
} | |
} | |
} | |
} | |
if (ibest >= 0) { | |
ggml_cuda_buffer& b = buffer_pool[ibest]; | |
void * ptr = b.ptr; | |
*actual_size = b.size; | |
b.ptr = nullptr; | |
b.size = 0; | |
return ptr; | |
} | |
void * ptr; | |
size_t look_ahead_size = (size_t) (1.05 * size); | |
look_ahead_size = 256 * ((look_ahead_size + 255)/256); | |
ggml_cuda_set_device(device); | |
CUDA_CHECK(ggml_cuda_device_malloc(&ptr, look_ahead_size, device)); | |
*actual_size = look_ahead_size; | |
pool_size += look_ahead_size; | |
#ifdef DEBUG_CUDA_MALLOC | |
GGML_LOG_INFO("%s[%d]: %d buffers, max_size = %u MB, pool_size = %u MB, requested %u MB\n", __func__, device, nnz, | |
(uint32_t)(max_size / 1024 / 1024), (uint32_t)(pool_size / 1024 / 1024), (uint32_t)(size / 1024 / 1024)); | |
#endif | |
return ptr; | |
} | |
void free(void * ptr, size_t size) override { | |
for (int i = 0; i < MAX_BUFFERS; ++i) { | |
ggml_cuda_buffer& b = buffer_pool[i]; | |
if (b.ptr == nullptr) { | |
b.ptr = ptr; | |
b.size = size; | |
return; | |
} | |
} | |
GGML_LOG_DEBUG(GGML_CUDA_NAME " buffer pool full, increase MAX_CUDA_BUFFERS\n"); | |
ggml_cuda_set_device(device); | |
CUDA_CHECK(cudaFree(ptr)); | |
pool_size -= size; | |
} | |
}; | |
// pool with virtual memory | |
#if !defined(GGML_USE_HIPBLAS) && !defined(GGML_CUDA_NO_VMM) | |
struct ggml_cuda_pool_vmm : public ggml_cuda_pool { | |
static const size_t CUDA_POOL_VMM_MAX_SIZE = 1ull << 35; // 32 GB | |
int device; | |
CUdeviceptr pool_addr = 0; | |
size_t pool_used = 0; | |
size_t pool_size = 0; | |
size_t granularity; | |
explicit ggml_cuda_pool_vmm(int device) : | |
device(device), | |
granularity(ggml_cuda_info().devices[device].vmm_granularity) { | |
} | |
~ggml_cuda_pool_vmm() { | |
if (pool_addr != 0) { | |
CU_CHECK(cuMemUnmap(pool_addr, pool_size)); | |
CU_CHECK(cuMemAddressFree(pool_addr, CUDA_POOL_VMM_MAX_SIZE)); | |
} | |
} | |
void * alloc(size_t size, size_t * actual_size) override { | |
// round up the allocation size to the alignment to ensure that all allocations are aligned for all data types | |
const size_t alignment = 128; | |
size = alignment * ((size + alignment - 1) / alignment); | |
size_t avail = pool_size - pool_used; | |
if (size > avail) { | |
// round up to the next multiple of the granularity | |
size_t reserve_size = size - avail; | |
reserve_size = granularity * ((reserve_size + granularity - 1) / granularity); | |
GGML_ASSERT(pool_size + reserve_size <= CUDA_POOL_VMM_MAX_SIZE); | |
// allocate more physical memory | |
CUmemAllocationProp prop = {}; | |
prop.type = CU_MEM_ALLOCATION_TYPE_PINNED; | |
prop.location.type = CU_MEM_LOCATION_TYPE_DEVICE; | |
prop.location.id = device; | |
CUmemGenericAllocationHandle handle; | |
CU_CHECK(cuMemCreate(&handle, reserve_size, &prop, 0)); | |
// reserve virtual address space (if not already reserved) | |
if (pool_addr == 0) { | |
CU_CHECK(cuMemAddressReserve(&pool_addr, CUDA_POOL_VMM_MAX_SIZE, 0, 0, 0)); | |
} | |
// map at the end of the pool | |
CU_CHECK(cuMemMap(pool_addr + pool_size, reserve_size, 0, handle, 0)); | |
// the memory allocation handle is no longer needed after mapping | |
CU_CHECK(cuMemRelease(handle)); | |
// set access | |
CUmemAccessDesc access = {}; | |
access.location.type = CU_MEM_LOCATION_TYPE_DEVICE; | |
access.location.id = device; | |
access.flags = CU_MEM_ACCESS_FLAGS_PROT_READWRITE; | |
CU_CHECK(cuMemSetAccess(pool_addr + pool_size, reserve_size, &access, 1)); | |
// add to the pool | |
pool_size += reserve_size; | |
//printf("cuda pool[%d]: size increased to %llu MB (reserved %llu MB)\n", | |
// device, (unsigned long long) (pool_size/1024/1024), | |
// (unsigned long long) (reserve_size/1024/1024)); | |
} | |
GGML_ASSERT(pool_addr != 0); | |
void * ptr = (void *) (pool_addr + pool_used); | |
*actual_size = size; | |
pool_used += size; | |
#ifdef DEBUG_CUDA_MALLOC | |
printf("cuda pool[%d]: allocated %llu bytes at %llx\n", device, (unsigned long long) size, ptr); | |
#endif | |
return ptr; | |
} | |
void free(void * ptr, size_t size) override { | |
#ifdef DEBUG_CUDA_MALLOC | |
printf("cuda pool[%d]: freed %llu bytes at %llx\n", device, (unsigned long long) size, ptr); | |
#endif | |
pool_used -= size; | |
// all deallocations must be in reverse order of the allocations | |
GGML_ASSERT(ptr == (void *) (pool_addr + pool_used)); | |
} | |
}; | |
#endif // !defined(GGML_USE_HIPBLAS) && !defined(GGML_CUDA_NO_VMM) | |
std::unique_ptr<ggml_cuda_pool> ggml_backend_cuda_context::new_pool_for_device(int device) { | |
#if !defined(GGML_USE_HIPBLAS) && !defined(GGML_CUDA_NO_VMM) | |
if (ggml_cuda_info().devices[device].vmm) { | |
return std::unique_ptr<ggml_cuda_pool>(new ggml_cuda_pool_vmm(device)); | |
} | |
#endif // !defined(GGML_USE_HIPBLAS) && !defined(GGML_CUDA_NO_VMM) | |
return std::unique_ptr<ggml_cuda_pool>(new ggml_cuda_pool_leg(device)); | |
} | |
// cuda buffer | |
struct ggml_backend_cuda_buffer_context { | |
int device; | |
void * dev_ptr = nullptr; | |
std::string name; | |
ggml_backend_cuda_buffer_context(int device, void * dev_ptr) : | |
device(device), dev_ptr(dev_ptr), | |
name(GGML_CUDA_NAME + std::to_string(device)) { | |
} | |
~ggml_backend_cuda_buffer_context() { | |
CUDA_CHECK(cudaFree(dev_ptr)); | |
} | |
}; | |
static void ggml_backend_cuda_buffer_free_buffer(ggml_backend_buffer_t buffer) { | |
ggml_backend_cuda_buffer_context * ctx = (ggml_backend_cuda_buffer_context *)buffer->context; | |
delete ctx; | |
} | |
static bool ggml_backend_buffer_is_cuda(ggml_backend_buffer_t buffer) { | |
return buffer->iface.free_buffer == ggml_backend_cuda_buffer_free_buffer; | |
} | |
static void * ggml_backend_cuda_buffer_get_base(ggml_backend_buffer_t buffer) { | |
ggml_backend_cuda_buffer_context * ctx = (ggml_backend_cuda_buffer_context *)buffer->context; | |
return ctx->dev_ptr; | |
} | |
static void ggml_backend_cuda_buffer_init_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor) { | |
ggml_backend_cuda_buffer_context * ctx = (ggml_backend_cuda_buffer_context *)buffer->context; | |
if (tensor->view_src != NULL) { | |
assert(tensor->view_src->buffer->buft == buffer->buft); | |
return; | |
} | |
if (ggml_is_quantized(tensor->type) && tensor->view_src == nullptr && ggml_backend_buffer_get_usage(buffer) != GGML_BACKEND_BUFFER_USAGE_COMPUTE) { | |
// initialize padding to 0 to avoid possible NaN values | |
size_t original_size = ggml_nbytes(tensor); | |
size_t padded_size = ggml_backend_buft_get_alloc_size(buffer->buft, tensor); | |
if (padded_size > original_size) { | |
ggml_cuda_set_device(ctx->device); | |
CUDA_CHECK(cudaMemset((char *)tensor->data + original_size, 0, padded_size - original_size)); | |
} | |
} | |
} | |
static void ggml_backend_cuda_buffer_memset_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor, uint8_t value, size_t offset, size_t size) { | |
ggml_backend_cuda_buffer_context * ctx = (ggml_backend_cuda_buffer_context *)buffer->context; | |
ggml_cuda_set_device(ctx->device); | |
CUDA_CHECK(cudaMemsetAsync((char *)tensor->data + offset, value, size, cudaStreamPerThread)); | |
CUDA_CHECK(cudaStreamSynchronize(cudaStreamPerThread)); | |
} | |
static void ggml_backend_cuda_buffer_set_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor, const void * data, size_t offset, size_t size) { | |
ggml_backend_cuda_buffer_context * ctx = (ggml_backend_cuda_buffer_context *)buffer->context; | |
ggml_cuda_set_device(ctx->device); | |
CUDA_CHECK(cudaMemcpyAsync((char *)tensor->data + offset, data, size, cudaMemcpyHostToDevice, cudaStreamPerThread)); | |
CUDA_CHECK(cudaStreamSynchronize(cudaStreamPerThread)); | |
} | |
static void ggml_backend_cuda_buffer_get_tensor(ggml_backend_buffer_t buffer, const ggml_tensor * tensor, void * data, size_t offset, size_t size) { | |
ggml_backend_cuda_buffer_context * ctx = (ggml_backend_cuda_buffer_context *)buffer->context; | |
ggml_cuda_set_device(ctx->device); | |
CUDA_CHECK(cudaMemcpyAsync(data, (const char *)tensor->data + offset, size, cudaMemcpyDeviceToHost, cudaStreamPerThread)); | |
CUDA_CHECK(cudaStreamSynchronize(cudaStreamPerThread)); | |
} | |
static bool ggml_backend_cuda_buffer_cpy_tensor(ggml_backend_buffer_t buffer, const ggml_tensor * src, ggml_tensor * dst) { | |
if (ggml_backend_buffer_is_cuda(src->buffer)) { | |
ggml_backend_cuda_buffer_context * src_ctx = (ggml_backend_cuda_buffer_context *)src->buffer->context; | |
ggml_backend_cuda_buffer_context * dst_ctx = (ggml_backend_cuda_buffer_context *)dst->buffer->context; | |
if (src_ctx->device == dst_ctx->device) { | |
CUDA_CHECK(cudaMemcpyAsync(dst->data, src->data, ggml_nbytes(src), cudaMemcpyDeviceToDevice, cudaStreamPerThread)); | |
} else { | |
#ifdef GGML_CUDA_NO_PEER_COPY | |
return false; | |
#else | |
CUDA_CHECK(cudaMemcpyPeerAsync(dst->data, dst_ctx->device, src->data, src_ctx->device, ggml_nbytes(src), cudaStreamPerThread)); | |
#endif | |
} | |
CUDA_CHECK(cudaStreamSynchronize(cudaStreamPerThread)); | |
return true; | |
} | |
return false; | |
GGML_UNUSED(buffer); | |
} | |
static void ggml_backend_cuda_buffer_clear(ggml_backend_buffer_t buffer, uint8_t value) { | |
ggml_backend_cuda_buffer_context * ctx = (ggml_backend_cuda_buffer_context *)buffer->context; | |
ggml_cuda_set_device(ctx->device); | |
CUDA_CHECK(cudaDeviceSynchronize()); | |
CUDA_CHECK(cudaMemset(ctx->dev_ptr, value, buffer->size)); | |
CUDA_CHECK(cudaDeviceSynchronize()); | |
} | |
static const ggml_backend_buffer_i ggml_backend_cuda_buffer_interface = { | |
/* .free_buffer = */ ggml_backend_cuda_buffer_free_buffer, | |
/* .get_base = */ ggml_backend_cuda_buffer_get_base, | |
/* .init_tensor = */ ggml_backend_cuda_buffer_init_tensor, | |
/* .memset_tensor = */ ggml_backend_cuda_buffer_memset_tensor, | |
/* .set_tensor = */ ggml_backend_cuda_buffer_set_tensor, | |
/* .get_tensor = */ ggml_backend_cuda_buffer_get_tensor, | |
/* .cpy_tensor = */ ggml_backend_cuda_buffer_cpy_tensor, | |
/* .clear = */ ggml_backend_cuda_buffer_clear, | |
/* .reset = */ NULL, | |
}; | |
// cuda buffer type | |
struct ggml_backend_cuda_buffer_type_context { | |
int device; | |
std::string name; | |
}; | |
static const char * ggml_backend_cuda_buffer_type_get_name(ggml_backend_buffer_type_t buft) { | |
ggml_backend_cuda_buffer_type_context * ctx = (ggml_backend_cuda_buffer_type_context *)buft->context; | |
return ctx->name.c_str(); | |
} | |
static bool ggml_backend_buft_is_cuda(ggml_backend_buffer_type_t buft) { | |
return buft->iface.get_name == ggml_backend_cuda_buffer_type_get_name; | |
} | |
static ggml_backend_buffer_t ggml_backend_cuda_buffer_type_alloc_buffer(ggml_backend_buffer_type_t buft, size_t size) { | |
ggml_backend_cuda_buffer_type_context * buft_ctx = (ggml_backend_cuda_buffer_type_context *)buft->context; | |
ggml_cuda_set_device(buft_ctx->device); | |
void * dev_ptr; | |
cudaError_t err = ggml_cuda_device_malloc(&dev_ptr, size, buft_ctx->device); | |
if (err != cudaSuccess) { | |
// clear the error | |
cudaGetLastError(); | |
GGML_LOG_ERROR("%s: allocating %.2f MiB on device %d: cudaMalloc failed: %s\n", __func__, size / 1024.0 / 1024.0, buft_ctx->device, cudaGetErrorString(err)); | |
return nullptr; | |
} | |
ggml_backend_cuda_buffer_context * ctx = new ggml_backend_cuda_buffer_context(buft_ctx->device, dev_ptr); | |
return ggml_backend_buffer_init(buft, ggml_backend_cuda_buffer_interface, ctx, size); | |
} | |
static size_t ggml_backend_cuda_buffer_type_get_alignment(ggml_backend_buffer_type_t buft) { | |
return 128; | |
GGML_UNUSED(buft); | |
} | |
static size_t ggml_backend_cuda_buffer_type_get_alloc_size(ggml_backend_buffer_type_t buft, const ggml_tensor * tensor) { | |
size_t size = ggml_nbytes(tensor); | |
int64_t ne0 = tensor->ne[0]; | |
if (ggml_is_quantized(tensor->type)) { | |
if (ne0 % MATRIX_ROW_PADDING != 0) { | |
size += ggml_row_size(tensor->type, MATRIX_ROW_PADDING - ne0 % MATRIX_ROW_PADDING); | |
} | |
} | |
return size; | |
GGML_UNUSED(buft); | |
} | |
static const ggml_backend_buffer_type_i ggml_backend_cuda_buffer_type_interface = { | |
/* .get_name = */ ggml_backend_cuda_buffer_type_get_name, | |
/* .alloc_buffer = */ ggml_backend_cuda_buffer_type_alloc_buffer, | |
/* .get_alignment = */ ggml_backend_cuda_buffer_type_get_alignment, | |
/* .get_max_size = */ NULL, // defaults to SIZE_MAX | |
/* .get_alloc_size = */ ggml_backend_cuda_buffer_type_get_alloc_size, | |
/* .is_host = */ NULL, | |
}; | |
ggml_backend_buffer_type_t ggml_backend_cuda_buffer_type(int device) { | |
static std::mutex mutex; | |
std::lock_guard<std::mutex> lock(mutex); | |
if (device >= ggml_backend_cuda_get_device_count()) { | |
return nullptr; | |
} | |
static ggml_backend_buffer_type ggml_backend_cuda_buffer_types[GGML_CUDA_MAX_DEVICES]; | |
static bool ggml_backend_cuda_buffer_type_initialized = false; | |
if (!ggml_backend_cuda_buffer_type_initialized) { | |
for (int i = 0; i < ggml_backend_cuda_get_device_count(); i++) { | |
ggml_backend_cuda_buffer_types[i] = { | |
/* .iface = */ ggml_backend_cuda_buffer_type_interface, | |
/* .device = */ ggml_backend_reg_dev_get(ggml_backend_cuda_reg(), i), | |
/* .context = */ new ggml_backend_cuda_buffer_type_context{i, GGML_CUDA_NAME + std::to_string(i)}, | |
}; | |
} | |
ggml_backend_cuda_buffer_type_initialized = true; | |
} | |
return &ggml_backend_cuda_buffer_types[device]; | |
} | |
// cuda split buffer | |
static int64_t get_row_rounding(const std::array<float, GGML_CUDA_MAX_DEVICES> & tensor_split) { | |
int64_t row_rounding = 0; | |
for (int id = 0; id < ggml_backend_cuda_get_device_count(); ++id) { | |
if (tensor_split[id] >= (id + 1 < ggml_backend_cuda_get_device_count() ? tensor_split[id + 1] : 1.0f)) { | |
continue; | |
} | |
const int cc = ggml_cuda_info().devices[id].cc; | |
row_rounding = std::max(row_rounding, (int64_t)get_mmq_y_host(cc)); | |
} | |
return row_rounding; | |
} | |
static void get_row_split(int64_t * row_low, int64_t * row_high, const ggml_tensor * tensor, const std::array<float, GGML_CUDA_MAX_DEVICES> & tensor_split, int id) { | |
const int64_t nrows = ggml_nrows(tensor); | |
const int64_t rounding = get_row_rounding(tensor_split); | |
*row_low = id == 0 ? 0 : nrows*tensor_split[id]; | |
*row_low -= *row_low % rounding; | |
if (id == ggml_backend_cuda_get_device_count() - 1) { | |
*row_high = nrows; | |
} else { | |
*row_high = nrows*tensor_split[id + 1]; | |
*row_high -= *row_high % rounding; | |
} | |
} | |
static size_t ggml_nbytes_split(const struct ggml_tensor * tensor, int nrows_split) { | |
static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); | |
return nrows_split*ggml_row_size(tensor->type, tensor->ne[0]); | |
} | |
struct ggml_backend_cuda_split_buffer_type_context { | |
int main_device; | |
std::array<float, GGML_CUDA_MAX_DEVICES> tensor_split; | |
std::string name; | |
}; | |
struct ggml_backend_cuda_split_buffer_context { | |
~ggml_backend_cuda_split_buffer_context() { | |
for (ggml_tensor_extra_gpu * extra : tensor_extras) { | |
for (int id = 0; id < GGML_CUDA_MAX_DEVICES; ++id) { | |
for (int64_t is = 0; is < GGML_CUDA_MAX_STREAMS; ++is) { | |
if (extra->events[id][is] != nullptr) { | |
CUDA_CHECK(cudaEventDestroy(extra->events[id][is])); | |
} | |
} | |
if (extra->data_device[id] != nullptr) { | |
CUDA_CHECK(cudaFree(extra->data_device[id])); | |
} | |
} | |
delete extra; | |
} | |
} | |
std::vector<ggml_tensor_extra_gpu *> tensor_extras; | |
}; | |
static void ggml_backend_cuda_split_buffer_free_buffer(ggml_backend_buffer_t buffer) { | |
ggml_backend_cuda_split_buffer_context * ctx = (ggml_backend_cuda_split_buffer_context *)buffer->context; | |
delete ctx; | |
} | |
static void * ggml_backend_cuda_split_buffer_get_base(ggml_backend_buffer_t buffer) { | |
// the pointers are stored in the tensor extras, this is just a dummy address and never dereferenced | |
return (void *)0x1000; | |
GGML_UNUSED(buffer); | |
} | |
static void ggml_backend_cuda_split_buffer_init_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor) { | |
GGML_ASSERT(tensor->view_src == nullptr); // views of split tensors are not supported | |
ggml_backend_cuda_split_buffer_context * ctx = (ggml_backend_cuda_split_buffer_context *)buffer->context; | |
ggml_backend_cuda_split_buffer_type_context * buft_ctx = (ggml_backend_cuda_split_buffer_type_context *)buffer->buft->context; | |
const int64_t ne0 = tensor->ne[0]; | |
ggml_tensor_extra_gpu * extra = new ggml_tensor_extra_gpu{}; | |
ctx->tensor_extras.push_back(extra); | |
for (int id = 0; id < ggml_backend_cuda_get_device_count(); ++id) { | |
int64_t row_low, row_high; | |
get_row_split(&row_low, &row_high, tensor, buft_ctx->tensor_split, id); | |
int64_t nrows_split = row_high - row_low; | |
if (nrows_split == 0) { | |
continue; | |
} | |
size_t size = ggml_nbytes_split(tensor, nrows_split); | |
const size_t original_size = size; | |
// pad last row to a multiple of 512 elements to avoid out-of-bounds memory accesses | |
if (ne0 % MATRIX_ROW_PADDING != 0) { | |
size += ggml_row_size(tensor->type, MATRIX_ROW_PADDING - ne0 % MATRIX_ROW_PADDING); | |
} | |
// FIXME: do not crash if cudaMalloc fails | |
// currently, init_tensor cannot fail, it needs to be fixed in ggml-backend first | |
ggml_cuda_set_device(id); | |
char * buf; | |
CUDA_CHECK(ggml_cuda_device_malloc((void**)&buf, size, id)); | |
// set padding to 0 to avoid possible NaN values | |
if (size > original_size) { | |
CUDA_CHECK(cudaMemset(buf + original_size, 0, size - original_size)); | |
} | |
extra->data_device[id] = buf; | |
for (int64_t is = 0; is < GGML_CUDA_MAX_STREAMS; ++is) { | |
CUDA_CHECK(cudaEventCreateWithFlags(&extra->events[id][is], cudaEventDisableTiming)); | |
} | |
} | |
tensor->extra = extra; | |
} | |
static void ggml_backend_cuda_split_buffer_set_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor, const void * data, size_t offset, size_t size) { | |
// split tensors must always be set in their entirety at once | |
GGML_ASSERT(offset == 0); | |
GGML_ASSERT(size == ggml_nbytes(tensor)); | |
ggml_backend_cuda_split_buffer_type_context * buft_ctx = (ggml_backend_cuda_split_buffer_type_context *)buffer->buft->context; | |
const int64_t ne0 = tensor->ne[0]; | |
const size_t nb1 = tensor->nb[1]; | |
ggml_tensor_extra_gpu * extra = (ggml_tensor_extra_gpu *)tensor->extra; | |
for (int id = 0; id < ggml_backend_cuda_get_device_count(); ++id) { | |
int64_t row_low, row_high; | |
get_row_split(&row_low, &row_high, tensor, buft_ctx->tensor_split, id); | |
int64_t nrows_split = row_high - row_low; | |
if (nrows_split == 0) { | |
continue; | |
} | |
const size_t offset_split = row_low*nb1; | |
size_t size = ggml_nbytes_split(tensor, nrows_split); | |
const size_t original_size = size; | |
// pad last row to a multiple of 512 elements to avoid out-of-bounds memory accesses | |
if (ne0 % MATRIX_ROW_PADDING != 0) { | |
size += ggml_row_size(tensor->type, MATRIX_ROW_PADDING - ne0 % MATRIX_ROW_PADDING); | |
} | |
const char * buf_host = (const char *)data + offset_split; | |
CUDA_CHECK(cudaMemcpyAsync(extra->data_device[id], buf_host, original_size, cudaMemcpyHostToDevice, cudaStreamPerThread)); | |
} | |
for (int id = 0; id < ggml_backend_cuda_get_device_count(); ++id) { | |
CUDA_CHECK(cudaStreamSynchronize(cudaStreamPerThread)); | |
} | |
} | |
static void ggml_backend_cuda_split_buffer_get_tensor(ggml_backend_buffer_t buffer, const ggml_tensor * tensor, void * data, size_t offset, size_t size) { | |
// split tensors must always be set in their entirety at once | |
GGML_ASSERT(offset == 0); | |
GGML_ASSERT(size == ggml_nbytes(tensor)); | |
ggml_backend_cuda_split_buffer_type_context * buft_ctx = (ggml_backend_cuda_split_buffer_type_context *)buffer->buft->context; | |
const int64_t ne0 = tensor->ne[0]; | |
const size_t nb1 = tensor->nb[1]; | |
ggml_tensor_extra_gpu * extra = (ggml_tensor_extra_gpu *)tensor->extra; | |
for (int id = 0; id < ggml_backend_cuda_get_device_count(); ++id) { | |
int64_t row_low, row_high; | |
get_row_split(&row_low, &row_high, tensor, buft_ctx->tensor_split, id); | |
int64_t nrows_split = row_high - row_low; | |
if (nrows_split == 0) { | |
continue; | |
} | |
const size_t offset_split = row_low*nb1; | |
size_t size = ggml_nbytes_split(tensor, nrows_split); | |
const size_t original_size = size; | |
// pad last row to a multiple of 512 elements to avoid out-of-bounds memory accesses | |
if (ne0 % MATRIX_ROW_PADDING != 0) { | |
size += ggml_row_size(tensor->type, MATRIX_ROW_PADDING - ne0 % MATRIX_ROW_PADDING); | |
} | |
char * buf_host = (char *)data + offset_split; | |
CUDA_CHECK(cudaMemcpyAsync(buf_host, extra->data_device[id], original_size, cudaMemcpyDeviceToHost, cudaStreamPerThread)); | |
} | |
for (int id = 0; id < ggml_backend_cuda_get_device_count(); ++id) { | |
CUDA_CHECK(cudaStreamSynchronize(cudaStreamPerThread)); | |
} | |
} | |
static void ggml_backend_cuda_split_buffer_clear(ggml_backend_buffer_t buffer, uint8_t value) { | |
GGML_UNUSED(buffer); | |
GGML_UNUSED(value); | |
} | |
static const ggml_backend_buffer_i ggml_backend_cuda_split_buffer_interface = { | |
/* .free_buffer = */ ggml_backend_cuda_split_buffer_free_buffer, | |
/* .get_base = */ ggml_backend_cuda_split_buffer_get_base, | |
/* .init_tensor = */ ggml_backend_cuda_split_buffer_init_tensor, | |
/* .memset_tensor = */ NULL, | |
/* .set_tensor = */ ggml_backend_cuda_split_buffer_set_tensor, | |
/* .get_tensor = */ ggml_backend_cuda_split_buffer_get_tensor, | |
/* .cpy_tensor = */ NULL, | |
/* .clear = */ ggml_backend_cuda_split_buffer_clear, | |
/* .reset = */ NULL, | |
}; | |
// cuda split buffer type | |
static const char * ggml_backend_cuda_split_buffer_type_get_name(ggml_backend_buffer_type_t buft) { | |
ggml_backend_cuda_split_buffer_type_context * ctx = (ggml_backend_cuda_split_buffer_type_context *)buft->context; | |
return ctx->name.c_str(); | |
} | |
static bool ggml_backend_buft_is_cuda_split(ggml_backend_buffer_type_t buft) { | |
return buft->iface.get_name == ggml_backend_cuda_split_buffer_type_get_name; | |
} | |
static ggml_backend_buffer_t ggml_backend_cuda_split_buffer_type_alloc_buffer(ggml_backend_buffer_type_t buft, size_t size) { | |
// since we don't know the exact split after rounding, we cannot allocate the device buffers at this point | |
// instead, we allocate them for each tensor separately in init_tensor | |
// however, the size still represents the maximum cumulative size of all the device buffers after the tensors are allocated, | |
// as returned by get_alloc_size. this limit is enforced during tensor allocation by ggml-alloc, so it must be correct. | |
ggml_backend_cuda_split_buffer_context * ctx = new ggml_backend_cuda_split_buffer_context(); | |
return ggml_backend_buffer_init(buft, ggml_backend_cuda_split_buffer_interface, ctx, size); | |
} | |
static size_t ggml_backend_cuda_split_buffer_type_get_alignment(ggml_backend_buffer_type_t buft) { | |
return 128; | |
GGML_UNUSED(buft); | |
} | |
static size_t ggml_backend_cuda_split_buffer_type_get_alloc_size(ggml_backend_buffer_type_t buft, const ggml_tensor * tensor) { | |
ggml_backend_cuda_split_buffer_type_context * ctx = (ggml_backend_cuda_split_buffer_type_context *)buft->context; | |
size_t total_size = 0; | |
const int64_t ne0 = tensor->ne[0]; | |
for (int id = 0; id < ggml_backend_cuda_get_device_count(); ++id) { | |
int64_t row_low, row_high; | |
get_row_split(&row_low, &row_high, tensor, ctx->tensor_split, id); | |
int64_t nrows_split = row_high - row_low; | |
if (nrows_split == 0) { | |
continue; | |
} | |
total_size += ggml_nbytes_split(tensor, nrows_split); | |
// pad last row to a multiple of 512 elements to avoid out-of-bounds memory accesses | |
if (ne0 % MATRIX_ROW_PADDING != 0) { | |
total_size += ggml_row_size(tensor->type, MATRIX_ROW_PADDING - ne0 % MATRIX_ROW_PADDING); | |
} | |
} | |
return total_size; | |
} | |
static bool ggml_backend_cuda_split_buffer_type_is_host(ggml_backend_buffer_type_t buft) { | |
return false; | |
GGML_UNUSED(buft); | |
} | |
static const ggml_backend_buffer_type_i ggml_backend_cuda_split_buffer_type_interface = { | |
/* .get_name = */ ggml_backend_cuda_split_buffer_type_get_name, | |
/* .alloc_buffer = */ ggml_backend_cuda_split_buffer_type_alloc_buffer, | |
/* .get_alignment = */ ggml_backend_cuda_split_buffer_type_get_alignment, | |
/* .get_max_size = */ NULL, // defaults to SIZE_MAX | |
/* .get_alloc_size = */ ggml_backend_cuda_split_buffer_type_get_alloc_size, | |
/* .is_host = */ ggml_backend_cuda_split_buffer_type_is_host, | |
}; | |
ggml_backend_buffer_type_t ggml_backend_cuda_split_buffer_type(int main_device, const float * tensor_split) { | |
static std::mutex mutex; | |
std::lock_guard<std::mutex> lock(mutex); | |
static std::map<std::pair<int, std::array<float, GGML_CUDA_MAX_DEVICES>>, struct ggml_backend_buffer_type> buft_map; | |
std::array<float, GGML_CUDA_MAX_DEVICES> tensor_split_arr = {}; | |
bool all_zero = tensor_split == nullptr || std::all_of(tensor_split, tensor_split + GGML_CUDA_MAX_DEVICES, [](float x) { return x == 0.0f; }); | |
if (all_zero) { | |
tensor_split_arr = ggml_cuda_info().default_tensor_split; | |
} else { | |
float split_sum = 0.0f; | |
for (int i = 0; i < ggml_backend_cuda_get_device_count(); ++i) { | |
tensor_split_arr[i] = split_sum; | |
split_sum += tensor_split[i]; | |
} | |
for (int i = 0; i < ggml_backend_cuda_get_device_count(); ++i) { | |
tensor_split_arr[i] /= split_sum; | |
} | |
} | |
auto it = buft_map.find({main_device, tensor_split_arr}); | |
if (it != buft_map.end()) { | |
return &it->second; | |
} | |
auto * ctx = new ggml_backend_cuda_split_buffer_type_context{ | |
main_device, | |
tensor_split_arr, | |
GGML_CUDA_NAME + std::to_string(main_device) + "_Split", | |
}; | |
struct ggml_backend_buffer_type buft { | |
/* .iface = */ ggml_backend_cuda_split_buffer_type_interface, | |
/* .device = */ ggml_backend_reg_dev_get(ggml_backend_cuda_reg(), main_device), | |
/* .context = */ ctx, | |
}; | |
auto result = buft_map.emplace(std::make_pair(main_device, tensor_split_arr), buft); | |
return &result.first->second; | |
} | |
// host buffer type | |
static const char * ggml_backend_cuda_host_buffer_type_name(ggml_backend_buffer_type_t buft) { | |
return GGML_CUDA_NAME "_Host"; | |
GGML_UNUSED(buft); | |
} | |
static void ggml_backend_cuda_host_buffer_free_buffer(ggml_backend_buffer_t buffer) { | |
CUDA_CHECK(cudaFreeHost(buffer->context)); | |
} | |
static void * ggml_cuda_host_malloc(size_t size) { | |
if (getenv("GGML_CUDA_NO_PINNED") != nullptr) { | |
return nullptr; | |
} | |
void * ptr = nullptr; | |
cudaError_t err = cudaMallocHost((void **) &ptr, size); | |
if (err != cudaSuccess) { | |
// clear the error | |
cudaGetLastError(); | |
GGML_LOG_DEBUG("%s: failed to allocate %.2f MiB of pinned memory: %s\n", __func__, | |
size / 1024.0 / 1024.0, cudaGetErrorString(err)); | |
return nullptr; | |
} | |
return ptr; | |
} | |
static ggml_backend_buffer_t ggml_backend_cuda_host_buffer_type_alloc_buffer(ggml_backend_buffer_type_t buft, size_t size) { | |
void * ptr = ggml_cuda_host_malloc(size); | |
if (ptr == nullptr) { | |
// fallback to cpu buffer | |
return ggml_backend_buft_alloc_buffer(ggml_backend_cpu_buffer_type(), size); | |
} | |
ggml_backend_buffer_t buffer = ggml_backend_cpu_buffer_from_ptr(ptr, size); | |
buffer->buft = buft; | |
buffer->iface.free_buffer = ggml_backend_cuda_host_buffer_free_buffer; | |
return buffer; | |
} | |
ggml_backend_buffer_type_t ggml_backend_cuda_host_buffer_type() { | |
static struct ggml_backend_buffer_type ggml_backend_cuda_buffer_type_host = { | |
/* .iface = */ { | |
/* .get_name = */ ggml_backend_cuda_host_buffer_type_name, | |
/* .alloc_buffer = */ ggml_backend_cuda_host_buffer_type_alloc_buffer, | |
/* .get_alignment = */ ggml_backend_cpu_buffer_type()->iface.get_alignment, | |
/* .get_max_size = */ NULL, // defaults to SIZE_MAX | |
/* .get_alloc_size = */ ggml_backend_cpu_buffer_type()->iface.get_alloc_size, | |
/* .is_host = */ ggml_backend_cpu_buffer_type()->iface.is_host, | |
}, | |
/* .device = */ ggml_backend_reg_dev_get(ggml_backend_cuda_reg(), 0), | |
/* .context = */ nullptr, | |
}; | |
return &ggml_backend_cuda_buffer_type_host; | |
} | |
//static bool ggml_backend_buffer_is_cuda_host(ggml_backend_buffer_t buffer) { | |
// return buffer->buft->iface.get_name == ggml_backend_cuda_host_buffer_type_name; | |
//} | |
/// kernels | |
typedef void (*ggml_cuda_op_mul_mat_t)( | |
ggml_backend_cuda_context & ctx, | |
const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst, const char * src0_dd_i, const float * src1_ddf_i, | |
const char * src1_ddq_i, float * dst_dd_i, const int64_t row_low, const int64_t row_high, const int64_t src1_ncols, | |
const int64_t src1_padded_row_size, cudaStream_t stream); | |
#ifndef GGML_CUDA_PEER_MAX_BATCH_SIZE | |
#define GGML_CUDA_PEER_MAX_BATCH_SIZE 128 | |
#endif // GGML_CUDA_PEER_MAX_BATCH_SIZE | |
#define MUL_MAT_SRC1_COL_STRIDE 128 | |
static __global__ void mul_mat_p021_f16_f32( | |
const void * __restrict__ vx, const float * __restrict__ y, float * __restrict__ dst, | |
const int ncols_x, const int nrows_x, const int nchannels_x, const int nchannels_y) { | |
const half * x = (const half *) vx; | |
const int row_x = blockDim.y*blockIdx.y + threadIdx.y; | |
const int channel = blockDim.z*blockIdx.z + threadIdx.z; | |
const int channel_x = channel / (nchannels_y / nchannels_x); | |
const int nrows_y = ncols_x; | |
const int nrows_dst = nrows_x; | |
const int row_dst = row_x; | |
float tmp = 0.0f; | |
for (int col_x0 = 0; col_x0 < ncols_x; col_x0 += blockDim.x) { | |
const int col_x = col_x0 + threadIdx.x; | |
if (col_x >= ncols_x) { | |
break; | |
} | |
// x is transposed and permuted | |
const int ix = row_x*nchannels_x*ncols_x + channel_x*ncols_x + col_x; | |
const float xi = __half2float(x[ix]); | |
const int row_y = col_x; | |
// y is not transposed but permuted | |
const int iy = channel*nrows_y + row_y; | |
tmp += xi * y[iy]; | |
} | |
// dst is not transposed and not permuted | |
const int idst = channel*nrows_dst + row_dst; | |
// sum up partial sums and write back result | |
tmp = warp_reduce_sum(tmp); | |
if (threadIdx.x == 0) { | |
dst[idst] = tmp; | |
} | |
} | |
static __global__ void mul_mat_vec_nc_f16_f32( // nc == non-contiguous | |
const void * __restrict__ vx, const float * __restrict__ y, float * __restrict__ dst, const int ncols_x, const int nrows_x, | |
const int row_stride_x, const int channel_stride_x, const int channel_x_divisor) { | |
const half * x = (const half *) vx; | |
const int row_x = blockDim.y*blockIdx.y + threadIdx.y; | |
const int channel = blockDim.z*blockIdx.z + threadIdx.z; | |
const int channel_x = channel / channel_x_divisor; | |
const int nrows_y = ncols_x; | |
const int nrows_dst = nrows_x; | |
const int row_dst = row_x; | |
const int idst = channel*nrows_dst + row_dst; | |
float tmp = 0.0f; | |
for (int col_x0 = 0; col_x0 < ncols_x; col_x0 += blockDim.x) { | |
const int col_x = col_x0 + threadIdx.x; | |
if (col_x >= ncols_x) { | |
break; | |
} | |
const int row_y = col_x; | |
const int ix = channel_x*channel_stride_x + row_x*row_stride_x + col_x; | |
const int iy = channel*nrows_y + row_y; | |
const float xi = __half2float(x[ix]); | |
tmp += xi * y[iy]; | |
} | |
// sum up partial sums and write back result | |
tmp = warp_reduce_sum(tmp); | |
if (threadIdx.x == 0) { | |
dst[idst] = tmp; | |
} | |
} | |
static void ggml_mul_mat_p021_f16_f32_cuda( | |
const void * vx, const float * y, float * dst, const int ncols_x, const int nrows_x, | |
const int nchannels_x, const int nchannels_y, cudaStream_t stream) { | |
const dim3 block_nums(1, nrows_x, nchannels_y); | |
const dim3 block_dims(WARP_SIZE, 1, 1); | |
mul_mat_p021_f16_f32<<<block_nums, block_dims, 0, stream>>>(vx, y, dst, ncols_x, nrows_x, nchannels_x, nchannels_y); | |
} | |
static void ggml_mul_mat_vec_nc_f16_f32_cuda( | |
const void * vx, const float * y, float * dst, const int ncols_x, const int nrows_x, const int row_stride_x, | |
const int nchannels_x, const int nchannels_y, const int channel_stride_x, cudaStream_t stream) { | |
const dim3 block_nums(1, nrows_x, nchannels_y); | |
const dim3 block_dims(WARP_SIZE, 1, 1); | |
mul_mat_vec_nc_f16_f32<<<block_nums, block_dims, 0, stream>>> | |
(vx, y, dst, ncols_x, nrows_x, row_stride_x, channel_stride_x, nchannels_y/nchannels_x); | |
} | |
static cudaError_t ggml_cuda_cpy_tensor_2d( | |
void * dst, const struct ggml_tensor * src, int64_t i3, int64_t i2, int64_t i1_low, int64_t i1_high, cudaStream_t stream) { | |
GGML_ASSERT(ggml_backend_buffer_is_cuda(src->buffer)); | |
const char * src_ptr = (const char *) src->data; | |
char * dst_ptr = (char *) dst; | |
const int64_t ne0 = src->ne[0]; | |
const int64_t nb0 = src->nb[0]; | |
const int64_t nb1 = src->nb[1]; | |
const int64_t nb2 = src->nb[2]; | |
const int64_t nb3 = src->nb[3]; | |
const enum ggml_type type = src->type; | |
const int64_t ts = ggml_type_size(type); | |
const int64_t bs = ggml_blck_size(type); | |
const int64_t i1_diff = i1_high - i1_low; | |
const char * x = src_ptr + i1_low*nb1 + i2*nb2 + i3*nb3; | |
if (nb0 == ts && nb1 == ts*ne0/bs) { | |
return cudaMemcpyAsync(dst_ptr, x, i1_diff*nb1, cudaMemcpyDeviceToDevice, stream); | |
} else if (nb0 == ts) { | |
return cudaMemcpy2DAsync(dst_ptr, ts*ne0/bs, x, nb1, ts*ne0/bs, i1_diff, cudaMemcpyDeviceToDevice, stream); | |
} else { | |
for (int64_t i1 = 0; i1 < i1_diff; i1++) { | |
const void * rx = (const void *) ((const char *) x + i1*nb1); | |
void * rd = (void *) (dst_ptr + i1*ts*ne0/bs); | |
// pretend the row is a matrix with cols=1 | |
cudaError_t r = cudaMemcpy2DAsync(rd, ts/bs, rx, nb0, ts/bs, ne0, cudaMemcpyDeviceToDevice, stream); | |
if (r != cudaSuccess) { | |
return r; | |
} | |
} | |
return cudaSuccess; | |
} | |
} | |
static void ggml_cuda_op_mul_mat_cublas( | |
ggml_backend_cuda_context & ctx, | |
const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst, const char * src0_dd_i, const float * src1_ddf_i, | |
const char * src1_ddq_i, float * dst_dd_i, const int64_t row_low, const int64_t row_high, const int64_t src1_ncols, | |
const int64_t src1_padded_row_size, cudaStream_t stream) { | |
GGML_ASSERT(src0_dd_i != nullptr); | |
GGML_ASSERT(src1_ddf_i != nullptr); | |
GGML_ASSERT(dst_dd_i != nullptr); | |
const int64_t ne00 = src0->ne[0]; | |
const int64_t ne10 = src1->ne[0]; | |
const int64_t ne0 = dst->ne[0]; | |
const int64_t row_diff = row_high - row_low; | |
int id = ggml_cuda_get_device(); | |
// the main device has a larger memory buffer to hold the results from all GPUs | |
// ldc == nrows of the matrix that cuBLAS writes into | |
int64_t ldc = id == ctx.device ? ne0 : row_diff; | |
const int compute_capability = ggml_cuda_info().devices[id].cc; | |
if (compute_capability >= CC_VOLTA && (src0->type == GGML_TYPE_F16 || ggml_is_quantized(src0->type)) && ggml_is_contiguous(src0) && row_diff == src0->ne[1] && dst->op_params[0] == GGML_PREC_DEFAULT) { | |
// convert src0 and src1 to fp16, multiply as fp16, convert dst to fp32 | |
ggml_cuda_pool_alloc<half> src0_as_f16(ctx.pool(id)); | |
if (src0->type != GGML_TYPE_F16) { | |
const to_fp16_cuda_t to_fp16_cuda = ggml_get_to_fp16_cuda(src0->type); | |
GGML_ASSERT(to_fp16_cuda != nullptr); | |
size_t ne = row_diff*ne00; | |
src0_as_f16.alloc(ne); | |
to_fp16_cuda(src0_dd_i, src0_as_f16.get(), ne, stream); | |
} | |
const half * src0_ptr = src0->type == GGML_TYPE_F16 ? (const half *) src0_dd_i : src0_as_f16.get(); | |
ggml_cuda_pool_alloc<half> src1_as_f16(ctx.pool(id)); | |
if (src1->type != GGML_TYPE_F16) { | |
const to_fp16_cuda_t to_fp16_cuda = ggml_get_to_fp16_cuda(src1->type); | |
GGML_ASSERT(to_fp16_cuda != nullptr); | |
size_t ne = src1_ncols*ne10; | |
src1_as_f16.alloc(ne); | |
to_fp16_cuda(src1_ddf_i, src1_as_f16.get(), ne, stream); | |
} | |
const half * src1_ptr = src1->type == GGML_TYPE_F16 ? (const half *) src1_ddf_i : src1_as_f16.get(); | |
ggml_cuda_pool_alloc<half> dst_f16(ctx.pool(id), row_diff*src1_ncols); | |
const half alpha_f16 = 1.0f; | |
const half beta_f16 = 0.0f; | |
CUBLAS_CHECK(cublasSetStream(ctx.cublas_handle(id), stream)); | |
CUBLAS_CHECK( | |
cublasGemmEx(ctx.cublas_handle(id), CUBLAS_OP_T, CUBLAS_OP_N, | |
row_diff, src1_ncols, ne10, | |
&alpha_f16, src0_ptr, CUDA_R_16F, ne00, | |
src1_ptr, CUDA_R_16F, ne10, | |
&beta_f16, dst_f16.get(), CUDA_R_16F, ldc, | |
CUBLAS_COMPUTE_16F, | |
CUBLAS_GEMM_DEFAULT_TENSOR_OP)); | |
const to_fp32_cuda_t to_fp32_cuda = ggml_get_to_fp32_cuda(GGML_TYPE_F16); | |
to_fp32_cuda(dst_f16.get(), dst_dd_i, row_diff*src1_ncols, stream); | |
} else { | |
ggml_cuda_pool_alloc<float> src0_ddq_as_f32(ctx.pool(id)); | |
ggml_cuda_pool_alloc<float> src1_ddq_as_f32(ctx.pool(id)); | |
if (src0->type != GGML_TYPE_F32) { | |
const to_fp32_cuda_t to_fp32_cuda = ggml_get_to_fp32_cuda(src0->type); | |
GGML_ASSERT(to_fp32_cuda != nullptr); | |
src0_ddq_as_f32.alloc(row_diff*ne00); | |
to_fp32_cuda(src0_dd_i, src0_ddq_as_f32.get(), row_diff*ne00, stream); | |
} | |
if (src1->type != GGML_TYPE_F32) { | |
const to_fp32_cuda_t to_fp32_cuda = ggml_get_to_fp32_cuda(src1->type); | |
GGML_ASSERT(to_fp32_cuda != nullptr); | |
src1_ddq_as_f32.alloc(src1_ncols*ne10); | |
to_fp32_cuda(src1_ddf_i, src1_ddq_as_f32.get(), src1_ncols*ne10, stream); | |
} | |
const float * src0_ddf_i = src0->type == GGML_TYPE_F32 ? (const float *) src0_dd_i : src0_ddq_as_f32.get(); | |
const float * src1_ddf1_i = src1->type == GGML_TYPE_F32 ? (const float *) src1_ddf_i : src1_ddq_as_f32.get(); | |
const float alpha = 1.0f; | |
const float beta = 0.0f; | |
CUBLAS_CHECK(cublasSetStream(ctx.cublas_handle(id), stream)); | |
CUBLAS_CHECK( | |
cublasSgemm(ctx.cublas_handle(id), CUBLAS_OP_T, CUBLAS_OP_N, | |
row_diff, src1_ncols, ne10, | |
&alpha, src0_ddf_i, ne00, | |
src1_ddf1_i, ne10, | |
&beta, dst_dd_i, ldc)); | |
} | |
GGML_UNUSED(dst); | |
GGML_UNUSED(src1_ddq_i); | |
GGML_UNUSED(src1_padded_row_size); | |
} | |
static void ggml_cuda_set_peer_access(const int n_tokens, int main_device) { | |
static bool peer_access_enabled = false; | |
const bool enable_peer_access = n_tokens <= GGML_CUDA_PEER_MAX_BATCH_SIZE; | |
if (peer_access_enabled == enable_peer_access) { | |
return; | |
} | |
#ifdef NDEBUG | |
for (int id = 0; id < ggml_backend_cuda_get_device_count(); ++id) { | |
ggml_cuda_set_device(id); | |
CUDA_CHECK(cudaDeviceSynchronize()); | |
} | |
for (int id = 0; id < ggml_backend_cuda_get_device_count(); ++id) { | |
ggml_cuda_set_device(id); | |
for (int id_other = 0; id_other < ggml_backend_cuda_get_device_count(); ++id_other) { | |
if (id == id_other) { | |
continue; | |
} | |
if (id != main_device && id_other != main_device) { | |
continue; | |
} | |
int can_access_peer; | |
CUDA_CHECK(cudaDeviceCanAccessPeer(&can_access_peer, id, id_other)); | |
if (can_access_peer) { | |
if (enable_peer_access) { | |
cudaError_t err = cudaDeviceEnablePeerAccess(id_other, 0); | |
if (err != cudaErrorPeerAccessAlreadyEnabled) { | |
CUDA_CHECK(err); | |
} else { | |
// reset the error | |
cudaGetLastError(); | |
} | |
} else { | |
cudaError_t err = cudaDeviceDisablePeerAccess(id_other); | |
if (err != cudaErrorPeerAccessNotEnabled) { | |
CUDA_CHECK(err); | |
} else { | |
// reset the error | |
cudaGetLastError(); | |
} | |
} | |
} | |
} | |
} | |
ggml_cuda_set_device(main_device); | |
#endif // NDEBUG | |
peer_access_enabled = enable_peer_access; | |
GGML_UNUSED(main_device); | |
} | |
static cudaError_t ggml_cuda_Memcpy2DPeerAsync( | |
void * dst, int dstDevice, size_t dpitch, void * src, int srcDevice, size_t spitch, size_t width, size_t height, cudaStream_t stream) { | |
#if !defined(GGML_USE_HIPBLAS) && !defined(GGML_USE_MUSA) | |
// cudaMemcpy2DAsync may fail with copies between vmm pools of different devices | |
cudaMemcpy3DPeerParms p = {}; | |
p.dstDevice = dstDevice; | |
p.dstPtr = make_cudaPitchedPtr(dst, dpitch, dpitch, height); | |
p.srcDevice = srcDevice; | |
p.srcPtr = make_cudaPitchedPtr(src, spitch, spitch, height); | |
p.extent = make_cudaExtent(width, height, 1); | |
return cudaMemcpy3DPeerAsync(&p, stream); | |
#else | |
// HIP does not support cudaMemcpy3DPeerAsync or vmm pools | |
GGML_UNUSED(dstDevice); | |
GGML_UNUSED(srcDevice); | |
return cudaMemcpy2DAsync(dst, dpitch, src, spitch, width, height, cudaMemcpyDeviceToDevice, stream); | |
#endif // !defined(GGML_USE_HIPBLAS) && !defined(GGML_USE_MUSA) | |
} | |
static void ggml_cuda_op_mul_mat( | |
ggml_backend_cuda_context & ctx, | |
const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst, ggml_cuda_op_mul_mat_t op, | |
quantize_cuda_t quantize_src1) { | |
const int64_t ne00 = src0->ne[0]; | |
const int64_t ne01 = src0->ne[1]; | |
const int64_t ne02 = src0->ne[2]; | |
const int64_t ne03 = src0->ne[3]; | |
const int64_t ne10 = src1->ne[0]; | |
const int64_t ne11 = src1->ne[1]; | |
const int64_t ne12 = src1->ne[2]; | |
const int64_t ne13 = src1->ne[3]; | |
const int64_t nrows1 = ggml_nrows(src1); | |
GGML_ASSERT(ne03 == ne13); | |
const int64_t ne0 = dst->ne[0]; | |
const int64_t ne1 = dst->ne[1]; | |
const int64_t nb2 = dst->nb[2]; | |
const int64_t nb3 = dst->nb[3]; | |
GGML_ASSERT(ggml_backend_buffer_is_cuda(dst->buffer)); | |
GGML_ASSERT(ggml_backend_buffer_is_cuda(src1->buffer)); | |
ggml_backend_cuda_buffer_context * src1_ctx = (ggml_backend_cuda_buffer_context *) src1->buffer->context; | |
ggml_backend_cuda_buffer_context * dst_ctx = (ggml_backend_cuda_buffer_context *) dst->buffer->context; | |
GGML_ASSERT(src1->type == GGML_TYPE_F32 || (src1->ne[2] == 1 && src1->ne[3] == 1)); | |
GGML_ASSERT(ne12 >= ne02 && ne12 % ne02 == 0); | |
const int64_t i02_divisor = ne12 / ne02; | |
const size_t src0_ts = ggml_type_size(src0->type); | |
const size_t src0_bs = ggml_blck_size(src0->type); | |
const size_t q8_1_ts = sizeof(block_q8_1); | |
const size_t q8_1_bs = QK8_1; | |
const bool src0_is_contiguous = ggml_is_contiguous(src0); | |
const bool src1_is_contiguous = ggml_is_contiguous(src1); | |
const int64_t src1_padded_col_size = GGML_PAD(ne10, MATRIX_ROW_PADDING); | |
const bool split = ggml_backend_buft_is_cuda_split(src0->buffer->buft); | |
GGML_ASSERT(!(split && ne02 > 1)); | |
GGML_ASSERT(!(split && ne03 > 1)); | |
GGML_ASSERT(!(split && ne02 < ne12)); | |
ggml_tensor_extra_gpu * src0_extra = split ? (ggml_tensor_extra_gpu *) src0->extra : nullptr; | |
std::array<float, GGML_CUDA_MAX_DEVICES> tensor_split; | |
if (split) { | |
ggml_backend_cuda_split_buffer_type_context * buft_ctx = (ggml_backend_cuda_split_buffer_type_context *) src0->buffer->buft->context; | |
tensor_split = buft_ctx->tensor_split; | |
} | |
struct dev_data { | |
int cc; | |
ggml_cuda_pool_alloc<char> src0_dd_alloc; | |
ggml_cuda_pool_alloc<float> src1_ddf_alloc; | |
ggml_cuda_pool_alloc<char> src1_ddq_alloc; | |
ggml_cuda_pool_alloc<float> dst_dd_alloc; | |
char * src0_dd = nullptr; | |
float * src1_ddf = nullptr; // float | |
char * src1_ddq = nullptr; // q8_1 | |
float * dst_dd = nullptr; | |
int64_t row_low; | |
int64_t row_high; | |
}; | |
dev_data dev[GGML_CUDA_MAX_DEVICES]; | |
int used_devices = 0; | |
for (int id = 0; id < ggml_backend_cuda_get_device_count(); ++id) { | |
dev[id].cc = ggml_cuda_info().devices[id].cc; | |
// by default, use all rows | |
dev[id].row_low = 0; | |
dev[id].row_high = ne01; | |
// for multi GPU, get the row boundaries from tensor split | |
// and round to mul_mat_q tile sizes | |
if (split) { | |
const int64_t rounding = get_row_rounding(tensor_split); | |
if (id != 0) { | |
dev[id].row_low = ne01*tensor_split[id]; | |
if (dev[id].row_low < ne01) { | |
dev[id].row_low -= dev[id].row_low % rounding; | |
} | |
} | |
if (id != ggml_backend_cuda_get_device_count() - 1) { | |
dev[id].row_high = ne01*tensor_split[id + 1]; | |
if (dev[id].row_high < ne01) { | |
dev[id].row_high -= dev[id].row_high % rounding; | |
} | |
} | |
} | |
} | |
for (int id = 0; id < ggml_backend_cuda_get_device_count(); ++id) { | |
if ((!split && id != ctx.device) || dev[id].row_low == dev[id].row_high) { | |
continue; | |
} | |
used_devices++; | |
const bool src1_on_device = id == src1_ctx->device; | |
const bool dst_on_device = id == dst_ctx->device; | |
ggml_cuda_set_device(id); | |
cudaStream_t stream = ctx.stream(id, 0); | |
if (src0_is_contiguous) { | |
dev[id].src0_dd = split ? (char *) src0_extra->data_device[id] : (char *) src0->data; | |
} else { | |
// If src0 is not contiguous it will be copied to a temporary buffer. | |
// This buffer needs to be cleared entirely because multiple regions will function as padding. | |
const size_t nbytes_data = ggml_nbytes(src0); | |
const size_t nbytes_padding = ggml_row_size(src0->type, MATRIX_ROW_PADDING - ne00 % MATRIX_ROW_PADDING); | |
dev[id].src0_dd = dev[id].src0_dd_alloc.alloc(ctx.pool(id), nbytes_data + nbytes_padding); | |
// TODO: remove this for MUSA once the Guilty Lockup issue is resolved | |
#ifndef GGML_USE_MUSA | |
CUDA_CHECK(cudaMemsetAsync(dev[id].src0_dd, 0, nbytes_data + nbytes_padding, stream)); | |
#else // GGML_USE_MUSA | |
CUDA_CHECK(cudaMemsetAsync(dev[id].src0_dd + nbytes_data, 0, nbytes_padding, stream)); | |
#endif // !GGML_USE_MUSA | |
} | |
// If src0 is on a temporary compute buffer (partial offloading) there may be some padding that needs to be cleared: | |
if (ne00 % MATRIX_ROW_PADDING != 0 && ggml_is_quantized(src0->type) && ggml_backend_buffer_get_usage(src0->buffer) == GGML_BACKEND_BUFFER_USAGE_COMPUTE && src0->view_src == nullptr) { | |
const size_t nbytes_data = ggml_row_size(src0->type, (dev[id].row_high - dev[id].row_low)*ne00); | |
const size_t nbytes_padding = ggml_row_size(src0->type, MATRIX_ROW_PADDING - ne00 % MATRIX_ROW_PADDING); | |
CUDA_CHECK(cudaMemsetAsync(dev[id].src0_dd + nbytes_data, 0, nbytes_padding, stream)); | |
} | |
if (src1_on_device && src1_is_contiguous) { | |
dev[id].src1_ddf = (float *) src1->data; | |
} else { | |
dev[id].src1_ddf = dev[id].src1_ddf_alloc.alloc(ctx.pool(id), ggml_nelements(src1)); | |
} | |
if (quantize_src1) { | |
size_t src_1_ddq_size = nrows1*src1_padded_col_size*q8_1_ts/q8_1_bs; | |
if (quantize_src1 == quantize_mmq_q8_1_cuda) { | |
src_1_ddq_size += get_mmq_x_max_host(dev[id].cc)*sizeof(block_q8_1_mmq); | |
} | |
dev[id].src1_ddq = dev[id].src1_ddq_alloc.alloc(ctx.pool(id), src_1_ddq_size); | |
if (src1_on_device && src1_is_contiguous) { | |
quantize_src1(dev[id].src1_ddf, dev[id].src1_ddq, ne10, ne11, ne12*ne13, src1_padded_col_size, src0->type, stream); | |
CUDA_CHECK(cudaGetLastError()); | |
} | |
} | |
if (dst_on_device) { | |
dev[id].dst_dd = (float *) dst->data; | |
} else { | |
const size_t size_dst_ddf = split ? (dev[id].row_high - dev[id].row_low)*ne1 : ggml_nelements(dst); | |
dev[id].dst_dd = dev[id].dst_dd_alloc.alloc(ctx.pool(id), size_dst_ddf); | |
} | |
} | |
// if multiple devices are used they need to wait for the main device | |
// here an event is recorded that signals that the main device has finished calculating the input data | |
if (split && used_devices > 1) { | |
ggml_cuda_set_device(ctx.device); | |
CUDA_CHECK(cudaEventRecord(src0_extra->events[ctx.device][0], ctx.stream())); | |
} | |
const int64_t src1_col_stride = split && used_devices > 1 ? MUL_MAT_SRC1_COL_STRIDE : ne11; | |
for (int64_t src1_col_0 = 0; src1_col_0 < ne11; src1_col_0 += src1_col_stride) { | |
const int64_t is = split ? (src1_col_0/src1_col_stride) % GGML_CUDA_MAX_STREAMS : 0; | |
const int64_t src1_ncols = src1_col_0 + src1_col_stride > ne11 ? ne11 - src1_col_0 : src1_col_stride; | |
for (int id = 0; id < ggml_backend_cuda_get_device_count(); ++id) { | |
if ((!split && id != ctx.device) || dev[id].row_low == dev[id].row_high) { | |
continue; | |
} | |
const bool src1_on_device = id == src1_ctx->device; | |
const bool dst_on_device = id == dst_ctx->device; | |
const int64_t row_diff = dev[id].row_high - dev[id].row_low; | |
ggml_cuda_set_device(id); | |
cudaStream_t stream = ctx.stream(id, is); | |
// wait for main GPU data if necessary | |
if (split && (id != ctx.device || is != 0)) { | |
CUDA_CHECK(cudaStreamWaitEvent(stream, src0_extra->events[ctx.device][0], 0)); | |
} | |
for (int64_t i0 = 0; i0 < ne13*ne12; ++i0) { | |
const int64_t i03 = i0 / ne12; | |
const int64_t i02 = i0 % ne12; | |
size_t src1_ddq_i_offset = i0*ne11 * src1_padded_col_size*q8_1_ts/q8_1_bs; | |
if (quantize_src1 == quantize_mmq_q8_1_cuda) { | |
src1_ddq_i_offset += src1_col_0 * sizeof(block_q8_1_mmq); | |
} else { | |
src1_ddq_i_offset += src1_col_0 * src1_padded_col_size*q8_1_ts/q8_1_bs; | |
} | |
// for split tensors the data begins at i0 == i0_offset_low | |
char * src0_dd_i = dev[id].src0_dd + (i0/i02_divisor) * (ne01*ne00*src0_ts)/src0_bs; | |
float * src1_ddf_i = dev[id].src1_ddf + (i0*ne11 + src1_col_0) * ne10; | |
char * src1_ddq_i = dev[id].src1_ddq + src1_ddq_i_offset; | |
float * dst_dd_i = dev[id].dst_dd + (i0*ne1 + src1_col_0) * (dst_on_device ? ne0 : row_diff); | |
// the main device memory buffer can be on VRAM scratch, with space for all partial results | |
// in that case an offset on dst_ddf_i is needed | |
if (id == ctx.device) { | |
dst_dd_i += dev[id].row_low; // offset is 0 if no tensor split | |
} | |
// copy src0, src1 to device if necessary | |
if (src1_is_contiguous) { | |
if (id != ctx.device) { | |
if (quantize_src1) { | |
char * src1_ddq_i_source = dev[ctx.device].src1_ddq + src1_ddq_i_offset; | |
if (quantize_src1 == quantize_mmq_q8_1_cuda) { | |
const size_t pitch = ne11*sizeof(block_q8_1_mmq); | |
const size_t width = src1_ncols*sizeof(block_q8_1_mmq); | |
const size_t height = src1_padded_col_size/(4*QK8_1); | |
CUDA_CHECK(ggml_cuda_Memcpy2DPeerAsync(src1_ddq_i, id, pitch, src1_ddq_i_source, ctx.device, pitch, width, height, stream)); | |
} else { | |
CUDA_CHECK(cudaMemcpyPeerAsync( | |
src1_ddq_i, id, src1_ddq_i_source, ctx.device, src1_ncols*src1_padded_col_size*q8_1_ts/q8_1_bs, stream)); | |
} | |
} else { | |
float * src1_ddf_i_source = (float *) src1->data; | |
src1_ddf_i_source += (i0*ne11 + src1_col_0) * ne10; | |
CUDA_CHECK(cudaMemcpyPeerAsync(src1_ddf_i, id, src1_ddf_i_source, ctx.device, | |
src1_ncols*ne10*sizeof(float), stream)); | |
} | |
} | |
} else if (src1_on_device && !src1_is_contiguous) { | |
CUDA_CHECK(ggml_cuda_cpy_tensor_2d( | |
src1_ddf_i, src1, i03, i02, src1_col_0, src1_col_0+src1_ncols, stream)); | |
} else { | |
GGML_ABORT("fatal error"); | |
} | |
if (quantize_src1 && !src1_is_contiguous) { | |
quantize_src1(src1_ddf_i, src1_ddq_i, ne10, src1_ncols, 1, src1_padded_col_size, src0->type, stream); | |
CUDA_CHECK(cudaGetLastError()); | |
} | |
if (src1_col_0 == 0 && !src0_is_contiguous && i02 % i02_divisor == 0) { | |
CUDA_CHECK(ggml_cuda_cpy_tensor_2d(src0_dd_i, src0, i03, i02/i02_divisor, dev[id].row_low, dev[id].row_high, stream)); | |
} | |
// do the computation | |
op(ctx, src0, src1, dst, src0_dd_i, src1_ddf_i, src1_ddq_i, dst_dd_i, | |
dev[id].row_low, dev[id].row_high, src1_ncols, src1_padded_col_size, stream); | |
CUDA_CHECK(cudaGetLastError()); | |
// copy dst to host or other device if necessary | |
if (!dst_on_device) { | |
void * dst_off_device = dst->data; | |
if (split) { | |
// src0 = weight matrix is saved as a transposed matrix for better memory layout. | |
// dst is NOT transposed. | |
// The outputs of matrix matrix multiplications can therefore NOT simply be concatenated for >1 GPU. | |
// Instead they need to be copied to the correct slice in ne0 = dst row index. | |
// If dst is a vector with ne0 == 1 then you don't have to do this but it still produces correct results. | |
float * dhf_dst_i = (float *) ((char *) dst_off_device + i02*nb2 + i03*nb3); | |
GGML_ASSERT(dst->nb[1] == ne0*sizeof(float)); | |
dhf_dst_i += src1_col_0*ne0 + dev[id].row_low; | |
CUDA_CHECK(ggml_cuda_Memcpy2DPeerAsync( | |
dhf_dst_i, ctx.device, ne0*sizeof(float), dst_dd_i, id, row_diff*sizeof(float), row_diff*sizeof(float), src1_ncols, stream)); | |
} else { | |
float * dhf_dst_i = (float *) ((char *) dst_off_device + i02*nb2 + i03*nb3); | |
GGML_ASSERT(dst->nb[1] == ne0*sizeof(float)); | |
dhf_dst_i += src1_col_0*ne0; | |
CUDA_CHECK(cudaMemcpyAsync(dhf_dst_i, dst_dd_i, src1_ncols*ne0*sizeof(float), cudaMemcpyDeviceToDevice, stream)); | |
} | |
} | |
// add event for the main device to wait on until other device is done | |
if (split && (id != ctx.device || is != 0)) { | |
CUDA_CHECK(cudaEventRecord(src0_extra->events[id][is], stream)); | |
} | |
} | |
} | |
} | |
// main device waits for all other devices to be finished | |
if (split && ggml_backend_cuda_get_device_count() > 1) { | |
int64_t is_max = (ne11 + MUL_MAT_SRC1_COL_STRIDE - 1) / MUL_MAT_SRC1_COL_STRIDE; | |
is_max = is_max <= GGML_CUDA_MAX_STREAMS ? is_max : GGML_CUDA_MAX_STREAMS; | |
ggml_cuda_set_device(ctx.device); | |
for (int id = 0; id < ggml_backend_cuda_get_device_count(); ++id) { | |
if (dev[id].row_low == dev[id].row_high) { | |
continue; | |
} | |
for (int64_t is = 0; is < is_max; ++is) { | |
CUDA_CHECK(cudaStreamWaitEvent(ctx.stream(), src0_extra->events[id][is], 0)); | |
} | |
} | |
} | |
} | |
static void ggml_cuda_mul_mat_vec_p021(ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst) { | |
GGML_ASSERT(ggml_is_permuted(src0) && ggml_is_permuted(src1)); | |
GGML_ASSERT(ggml_backend_buffer_is_cuda(src0->buffer)); | |
GGML_ASSERT(src0->nb[0] <= src0->nb[1] && src0->nb[2] <= src0->nb[3]); // 0213 permutation | |
GGML_ASSERT(src1->nb[0] <= src1->nb[1] && src1->nb[2] <= src1->nb[3]); // 0213 permutation | |
GGML_ASSERT(src0->type == GGML_TYPE_F16); | |
GGML_ASSERT(src1->type == GGML_TYPE_F32); | |
const int64_t ne00 = src0->ne[0]; | |
const int64_t ne01 = src0->ne[1]; | |
const int64_t ne02 = src0->ne[2]; | |
const int64_t ne12 = src1->ne[2]; | |
cudaStream_t main_stream = ctx.stream(); | |
void * src0_ddq = src0->data; | |
float * src1_ddf = (float *) src1->data; | |
float * dst_ddf = (float *) dst->data; | |
ggml_mul_mat_p021_f16_f32_cuda(src0_ddq, src1_ddf, dst_ddf, ne00, ne01, ne02, ne12, main_stream); | |
} | |
static void ggml_cuda_mul_mat_vec_nc(ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst) { | |
GGML_ASSERT(!ggml_is_transposed(src0)); | |
GGML_ASSERT(!ggml_is_transposed(src1)); | |
GGML_ASSERT(!ggml_is_permuted(src0)); | |
GGML_ASSERT(ggml_backend_buffer_is_cuda(src0->buffer)); | |
GGML_ASSERT(src0->type == GGML_TYPE_F16); | |
GGML_ASSERT(src1->type == GGML_TYPE_F32); | |
const int64_t ne00 = src0->ne[0]; | |
const int64_t ne01 = src0->ne[1]; | |
const int64_t ne02 = src0->ne[2]; | |
const int64_t nb01 = src0->nb[1]; | |
const int64_t nb02 = src0->nb[2]; | |
const int64_t ne12 = src1->ne[2]; | |
cudaStream_t main_stream = ctx.stream(); | |
void * src0_ddq = src0->data; | |
float * src1_ddf = (float *) src1->data; | |
float * dst_ddf = (float *) dst->data; | |
const int64_t row_stride_x = nb01 / sizeof(half); | |
const int64_t channel_stride_x = nb02 / sizeof(half); | |
ggml_mul_mat_vec_nc_f16_f32_cuda(src0_ddq, src1_ddf, dst_ddf, ne00, ne01, row_stride_x, ne02, ne12, channel_stride_x, main_stream); | |
} | |
static __global__ void k_compute_batched_ptrs( | |
const half * src0_as_f16, const half * src1_as_f16, char * dst, | |
const void ** ptrs_src, void ** ptrs_dst, | |
int64_t ne12, int64_t ne13, | |
int64_t ne23, | |
size_t nb02, size_t nb03, | |
size_t nb12, size_t nb13, | |
size_t nbd2, size_t nbd3, | |
int64_t r2, int64_t r3) { | |
int64_t i13 = blockIdx.x * blockDim.x + threadIdx.x; | |
int64_t i12 = blockIdx.y * blockDim.y + threadIdx.y; | |
if (i13 >= ne13 || i12 >= ne12) { | |
return; | |
} | |
int64_t i03 = i13 / r3; | |
int64_t i02 = i12 / r2; | |
ptrs_src[0*ne23 + i12 + i13*ne12] = (const char *) src0_as_f16 + i02*nb02 + i03*nb03; | |
ptrs_src[1*ne23 + i12 + i13*ne12] = (const char *) src1_as_f16 + i12*nb12 + i13*nb13; | |
ptrs_dst[0*ne23 + i12 + i13*ne12] = ( char *) dst + i12*nbd2 + i13*nbd3; | |
} | |
static void ggml_cuda_mul_mat_batched_cublas(ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst) { | |
GGML_ASSERT(!ggml_is_transposed(src0)); | |
GGML_ASSERT(!ggml_is_transposed(src1)); | |
GGML_ASSERT(ggml_backend_buffer_is_cuda(src0->buffer)); | |
GGML_ASSERT(src0->type == GGML_TYPE_F16); | |
GGML_TENSOR_BINARY_OP_LOCALS | |
const int64_t ne_dst = ggml_nelements(dst); | |
cudaStream_t main_stream = ctx.stream(); | |
CUBLAS_CHECK(cublasSetStream(ctx.cublas_handle(), main_stream)); | |
void * src0_ddq = src0->data; | |
half * src0_f16 = (half *) src0_ddq; | |
float * src1_ddf = (float *) src1->data; | |
float * dst_ddf = (float *) dst->data; | |
// convert src1 to fp16 | |
ggml_cuda_pool_alloc<half> src1_f16_alloc(ctx.pool()); | |
if (src1->type != GGML_TYPE_F16) { | |
const to_fp16_cuda_t to_fp16_cuda = ggml_get_to_fp16_cuda(src1->type); | |
const int64_t ne_src1 = ggml_nelements(src1); | |
src1_f16_alloc.alloc(ne_src1); | |
GGML_ASSERT(to_fp16_cuda != nullptr); | |
to_fp16_cuda(src1_ddf, src1_f16_alloc.get(), ne_src1, main_stream); | |
} | |
half * src1_f16 = src1->type == GGML_TYPE_F16 ? (half *) src1_ddf : src1_f16_alloc.get(); | |
ggml_cuda_pool_alloc<half> dst_f16(ctx.pool()); | |
char * dst_t; | |
cublasComputeType_t cu_compute_type = CUBLAS_COMPUTE_16F; | |
cudaDataType_t cu_data_type = CUDA_R_16F; | |
// dst strides | |
size_t nbd2 = dst->nb[2]; | |
size_t nbd3 = dst->nb[3]; | |
const half alpha_f16 = 1.0f; | |
const half beta_f16 = 0.0f; | |
const float alpha_f32 = 1.0f; | |
const float beta_f32 = 0.0f; | |
const void * alpha = &alpha_f16; | |
const void * beta = &beta_f16; | |
if (dst->op_params[0] == GGML_PREC_DEFAULT) { | |
dst_t = (char *) dst_f16.alloc(ne_dst); | |
nbd2 /= sizeof(float) / sizeof(half); | |
nbd3 /= sizeof(float) / sizeof(half); | |
} else { | |
dst_t = (char *) dst_ddf; | |
cu_compute_type = CUBLAS_COMPUTE_32F; | |
cu_data_type = CUDA_R_32F; | |
alpha = &alpha_f32; | |
beta = &beta_f32; | |
} | |
GGML_ASSERT(ne12 % ne02 == 0); | |
GGML_ASSERT(ne13 % ne03 == 0); | |
// broadcast factors | |
const int64_t r2 = ne12/ne02; | |
const int64_t r3 = ne13/ne03; | |
#if 0 | |
// use cublasGemmEx | |
{ | |
for (int i13 = 0; i13 < ne13; ++i13) { | |
for (int i12 = 0; i12 < ne12; ++i12) { | |
int i03 = i13 / r3; | |
int i02 = i12 / r2; | |
CUBLAS_CHECK( | |
cublasGemmEx(g_cublas_handles[g_main_device], CUBLAS_OP_T, CUBLAS_OP_N, | |
ne01, ne11, ne10, | |
alpha, (const char *) src0_as_f16 + i02*src0->nb[2] + i03*src0->nb[3] , CUDA_R_16F, nb01/sizeof(half), | |
(const char *) src1_as_f16 + i12*src1->nb[2]/2 + i13*src1->nb[3]/2, CUDA_R_16F, nb11/sizeof(float), | |
beta, ( char *) dst_t + i12*nbd2 + i13*nbd3, cu_data_type, ne01, | |
cu_compute_type, | |
CUBLAS_GEMM_DEFAULT_TENSOR_OP)); | |
} | |
} | |
} | |
#else | |
#ifdef GGML_USE_MUSA | |
GGML_ASSERT(false); | |
#else // !GGML_USE_MUSA | |
if (r2 == 1 && r3 == 1 && ggml_is_contiguous_2(src0) && ggml_is_contiguous_2(src1)) { | |
// there is no broadcast and src0, src1 are contiguous across dims 2, 3 | |
// use cublasGemmStridedBatchedEx | |
CUBLAS_CHECK( | |
cublasGemmStridedBatchedEx(ctx.cublas_handle(), CUBLAS_OP_T, CUBLAS_OP_N, | |
ne01, ne11, ne10, | |
alpha, (const char *) src0_f16, CUDA_R_16F, nb01/nb00, nb02/nb00, // strideA | |
(const char *) src1_f16, CUDA_R_16F, nb11/nb10, nb12/nb10, // strideB | |
beta, ( char *) dst_t, cu_data_type, ne01, nb2/nb0, // strideC | |
ne12*ne13, | |
cu_compute_type, | |
CUBLAS_GEMM_DEFAULT_TENSOR_OP)); | |
} else { | |
// use cublasGemmBatchedEx | |
const int ne23 = ne12*ne13; | |
ggml_cuda_pool_alloc<const void *> ptrs_src(ctx.pool(), 2*ne23); | |
ggml_cuda_pool_alloc< void *> ptrs_dst(ctx.pool(), 1*ne23); | |
dim3 block_dims(ne13, ne12); | |
k_compute_batched_ptrs<<<1, block_dims, 0, main_stream>>>( | |
src0_f16, src1_f16, dst_t, | |
ptrs_src.get(), ptrs_dst.get(), | |
ne12, ne13, | |
ne23, | |
nb02, nb03, | |
src1->type == GGML_TYPE_F16 ? nb12 : nb12/2, | |
src1->type == GGML_TYPE_F16 ? nb13 : nb13/2, | |
nbd2, nbd3, | |
r2, r3); | |
CUDA_CHECK(cudaGetLastError()); | |
CUBLAS_CHECK( | |
cublasGemmBatchedEx(ctx.cublas_handle(), CUBLAS_OP_T, CUBLAS_OP_N, | |
ne01, ne11, ne10, | |
alpha, (const void **) (ptrs_src.get() + 0*ne23), CUDA_R_16F, nb01/nb00, | |
(const void **) (ptrs_src.get() + 1*ne23), CUDA_R_16F, nb11/nb10, | |
beta, ( void **) (ptrs_dst.get() + 0*ne23), cu_data_type, ne01, | |
ne23, | |
cu_compute_type, | |
CUBLAS_GEMM_DEFAULT_TENSOR_OP)); | |
} | |
#endif // GGML_USE_MUSA | |
#endif | |
if (dst->op_params[0] == GGML_PREC_DEFAULT) { | |
const to_fp32_cuda_t to_fp32_cuda = ggml_get_to_fp32_cuda(GGML_TYPE_F16); | |
to_fp32_cuda(dst_f16.get(), dst_ddf, ne_dst, main_stream); | |
} | |
} | |
static void ggml_cuda_mul_mat(ggml_backend_cuda_context & ctx, const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst) { | |
const bool split = ggml_backend_buft_is_cuda_split(src0->buffer->buft); | |
bool use_dequantize_mul_mat_vec = ggml_cuda_dmmv_type_supported(src0->type) | |
&& src1->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32 | |
&& src0->ne[0] % (GGML_CUDA_DMMV_X*2) == 0 && src1->ne[1] == 1; | |
bool use_mul_mat_vec_q = ggml_is_quantized(src0->type) | |
&& src1->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32 | |
&& src1->ne[1] <= MMVQ_MAX_BATCH_SIZE; | |
bool use_mul_mat_q = ggml_is_quantized(src0->type) | |
&& src1->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32; | |
// if mmvq is available it's a better choice than dmmv: | |
#ifndef GGML_CUDA_FORCE_DMMV | |
use_dequantize_mul_mat_vec = use_dequantize_mul_mat_vec && !use_mul_mat_vec_q; | |
#endif // GGML_CUDA_FORCE_DMMV | |
bool any_gpus_with_slow_fp16 = false; | |
if (split) { | |
ggml_backend_cuda_split_buffer_type_context * buft_ctx = (ggml_backend_cuda_split_buffer_type_context *) src0->buffer->buft->context; | |
auto & tensor_split = buft_ctx->tensor_split; | |
for (int id = 0; id < ggml_backend_cuda_get_device_count(); ++id) { | |
// skip devices that are not going to do any work: | |
if (tensor_split[id] >= (id + 1 < ggml_backend_cuda_get_device_count() ? tensor_split[id + 1] : 1.0f)) { | |
continue; | |
} | |
const int cc = ggml_cuda_info().devices[id].cc; | |
use_mul_mat_q = use_mul_mat_q && ggml_cuda_should_use_mmq(src0->type, cc, src1->ne[1]); | |
any_gpus_with_slow_fp16 = any_gpus_with_slow_fp16 || !fast_fp16_available(cc); | |
} | |
} else { | |
const int cc = ggml_cuda_info().devices[ctx.device].cc; | |
use_mul_mat_q = use_mul_mat_q && ggml_cuda_should_use_mmq(src0->type, cc, src1->ne[1]); | |
any_gpus_with_slow_fp16 = any_gpus_with_slow_fp16 || !fast_fp16_available(cc); | |
} | |
// debug helpers | |
//printf("src0: %8d %8d %8d %8d\n", src0->ne[0], src0->ne[1], src0->ne[2], src0->ne[3]); | |
//printf(" %8d %8d %8d %8d\n", src0->nb[0], src0->nb[1], src0->nb[2], src0->nb[3]); | |
//printf("src1: %8d %8d %8d %8d\n", src1->ne[0], src1->ne[1], src1->ne[2], src1->ne[3]); | |
//printf(" %8d %8d %8d %8d\n", src1->nb[0], src1->nb[1], src1->nb[2], src1->nb[3]); | |
//printf("src0 is contiguous %d, transposed %d, type = %s, name = %s\n", ggml_is_contiguous(src0), ggml_is_transposed(src0), ggml_type_name(src0->type), src0->name); | |
//printf("src1 is contiguous %d, transposed %d, type = %s, name = %s\n", ggml_is_contiguous(src1), ggml_is_transposed(src1), ggml_type_name(src1->type), src1->name); | |
if (!split && any_gpus_with_slow_fp16 && src0->type == GGML_TYPE_F16 && ggml_is_permuted(src0) && ggml_is_permuted(src1) && src1->ne[1] == 1) { | |
// FP32 precision KQ single-batch for batch size 1 without FlashAttention | |
ggml_cuda_mul_mat_vec_p021(ctx, src0, src1, dst); | |
} else if (!split && any_gpus_with_slow_fp16 && src0->type == GGML_TYPE_F16 && !ggml_is_contiguous(src0) && !ggml_is_transposed(src1) && src1->ne[1] == 1) { | |
// FP32 precision KQV single-batch for batch size 1 without FlashAttention | |
ggml_cuda_mul_mat_vec_nc(ctx, src0, src1, dst); | |
} else if (!split && src0->type == GGML_TYPE_F16 && (src1->type == GGML_TYPE_F16 || !any_gpus_with_slow_fp16) | |
&& !ggml_is_transposed(src0) && !ggml_is_transposed(src1) && src1->ne[2]*src1->ne[3] > 1) { | |
// KQ + KQV multi-batch without FlashAttention | |
ggml_cuda_mul_mat_batched_cublas(ctx, src0, src1, dst); | |
} else if (use_dequantize_mul_mat_vec) { | |
ggml_cuda_op_mul_mat(ctx, src0, src1, dst, ggml_cuda_op_dequantize_mul_mat_vec, nullptr); | |
} else if (use_mul_mat_vec_q) { | |
ggml_cuda_op_mul_mat(ctx, src0, src1, dst, ggml_cuda_op_mul_mat_vec_q, quantize_row_q8_1_cuda); | |
} else if (use_mul_mat_q) { | |
ggml_cuda_op_mul_mat(ctx, src0, src1, dst, ggml_cuda_op_mul_mat_q, quantize_mmq_q8_1_cuda); | |
} else { | |
ggml_cuda_op_mul_mat(ctx, src0, src1, dst, ggml_cuda_op_mul_mat_cublas, nullptr); | |
} | |
} | |
struct mmid_row_mapping { | |
int32_t i1; | |
int32_t i2; | |
}; | |
static __global__ void k_copy_src1_to_contiguous(const char * __restrict__ src1_original, char * __restrict__ src1_contiguous, | |
int * __restrict__ cur_src1_row, mmid_row_mapping * __restrict__ row_mapping, | |
const char * __restrict ids, int64_t i02, size_t ids_nb1, size_t ids_nb0, | |
int64_t ne11, int64_t ne10, | |
size_t nb11, size_t nb12) { | |
int32_t iid1 = blockIdx.x; | |
int32_t id = blockIdx.y; | |
const int32_t row_id_i = *(const int32_t *) (ids + iid1*ids_nb1 + id*ids_nb0); | |
if (row_id_i != i02) { | |
return; | |
} | |
const int64_t i11 = id % ne11; | |
const int64_t i12 = iid1; | |
__shared__ int src1_row; | |
if (threadIdx.x == 0) { | |
src1_row = atomicAdd(cur_src1_row, 1); | |
row_mapping[src1_row] = {id, iid1}; | |
} | |
__syncthreads(); | |
const float * src1_row_original = (const float *)(src1_original + i11*nb11 + i12*nb12); | |
float * src1_row_contiguous = (float *)(src1_contiguous + src1_row*nb11); | |
for (int i = threadIdx.x; i < ne10; i += blockDim.x) { | |
src1_row_contiguous[i] = src1_row_original[i]; | |
} | |
} | |
static __global__ void k_copy_dst_from_contiguous(char * __restrict__ dst_original, const char * __restrict__ dst_contiguous, | |
const mmid_row_mapping * __restrict__ row_mapping, | |
int64_t ne0, | |
size_t nb1, size_t nb2) { | |
int32_t i = blockIdx.x; | |
const int32_t i1 = row_mapping[i].i1; | |
const int32_t i2 = row_mapping[i].i2; | |
const float * dst_row_contiguous = (const float *)(dst_contiguous + i*nb1); | |
float * dst_row_original = (float *)(dst_original + i1*nb1 + i2*nb2); | |
for (int j = threadIdx.x; j < ne0; j += blockDim.x) { | |
dst_row_original[j] = dst_row_contiguous[j]; | |
} | |
} | |
static void ggml_cuda_mul_mat_id(ggml_backend_cuda_context & ctx, ggml_tensor * dst) { | |
const ggml_tensor * src0 = dst->src[0]; | |
const ggml_tensor * src1 = dst->src[1]; | |
const ggml_tensor * ids = dst->src[2]; | |
GGML_TENSOR_BINARY_OP_LOCALS | |
GGML_ASSERT(!ggml_backend_buft_is_cuda_split(src0->buffer->buft) && "mul_mat_id does not support split buffers"); | |
cudaStream_t stream = ctx.stream(); | |
const int64_t n_as = ne02; | |
const int64_t n_ids = ids->ne[0]; | |
std::vector<char> ids_host(ggml_nbytes(ids)); | |
const char * ids_dev = (const char *) ids->data; | |
CUDA_CHECK(cudaMemcpyAsync(ids_host.data(), ids_dev, ggml_nbytes(ids), cudaMemcpyDeviceToHost, stream)); | |
CUDA_CHECK(cudaStreamSynchronize(stream)); | |
ggml_tensor src0_row = *src0; | |
ggml_tensor src1_row = *src1; | |
ggml_tensor dst_row = *dst; | |
char * src0_original = (char *) src0->data; | |
char * src1_original = (char *) src1->data; | |
char * dst_original = (char *) dst->data; | |
src0_row.ne[2] = 1; | |
src0_row.ne[3] = 1; | |
src0_row.nb[3] = nb02; | |
src1_row.ne[1] = 1; | |
src1_row.ne[2] = 1; | |
src1_row.ne[3] = 1; | |
src1_row.nb[2] = nb11; | |
src1_row.nb[3] = nb11; | |
dst_row.ne[1] = 1; | |
dst_row.ne[2] = 1; | |
dst_row.ne[3] = 1; | |
dst_row.nb[2] = nb1; | |
dst_row.nb[3] = nb1; | |
if (ne12 == 1) { | |
for (int64_t iid1 = 0; iid1 < ids->ne[1]; iid1++) { | |
for (int64_t id = 0; id < n_ids; id++) { | |
const int32_t i02 = *(const int32_t *) (ids_host.data() + iid1*ids->nb[1] + id*ids->nb[0]); | |
GGML_ASSERT(i02 >= 0 && i02 < n_as); | |
const int64_t i11 = id % ne11; | |
const int64_t i12 = iid1; | |
const int64_t i1 = id; | |
const int64_t i2 = i12; | |
src0_row.data = src0_original + i02*nb02; | |
src1_row.data = src1_original + i11*nb11 + i12*nb12; | |
dst_row.data = dst_original + i1*nb1 + i2*nb2; | |
ggml_cuda_mul_mat(ctx, &src0_row, &src1_row, &dst_row); | |
} | |
} | |
} else { | |
ggml_cuda_pool_alloc<char> src1_contiguous(ctx.pool(), sizeof(float)*ggml_nelements(src1)); | |
ggml_cuda_pool_alloc<char> dst_contiguous(ctx.pool(), sizeof(float)*ggml_nelements(dst)); | |
src1_row.data = src1_contiguous.get(); | |
dst_row.data = dst_contiguous.get(); | |
for (int64_t i02 = 0; i02 < n_as; i02++) { | |
int64_t num_src1_rows = 0; | |
for (int64_t iid1 = 0; iid1 < ids->ne[1]; iid1++) { | |
for (int64_t id = 0; id < n_ids; id++) { | |
const int32_t row_id_i = *(const int32_t *) (ids_host.data() + iid1*ids->nb[1] + id*ids->nb[0]); | |
GGML_ASSERT(row_id_i >= 0 && row_id_i < n_as); | |
if (row_id_i != i02) { | |
continue; | |
} | |
num_src1_rows++; | |
} | |
} | |
if (num_src1_rows == 0) { | |
continue; | |
} | |
ggml_cuda_pool_alloc<int> dev_cur_src1_row(ctx.pool(), 1); | |
ggml_cuda_pool_alloc<mmid_row_mapping> dev_row_mapping(ctx.pool(), num_src1_rows); | |
CUDA_CHECK(cudaMemsetAsync(dev_cur_src1_row.get(), 0, sizeof(int), stream)); | |
{ | |
dim3 block_dims(std::min((unsigned int)ne10, 768u)); | |
dim3 grid_dims(ids->ne[1], n_ids); | |
k_copy_src1_to_contiguous<<<grid_dims, block_dims, 0, stream>>>( | |
src1_original, src1_contiguous.get(), | |
dev_cur_src1_row.get(), dev_row_mapping.get(), | |
ids_dev, i02, ids->nb[1], ids->nb[0], | |
ne11, ne10, | |
nb11, nb12); | |
CUDA_CHECK(cudaGetLastError()); | |
} | |
src0_row.data = src0_original + i02*nb02; | |
GGML_ASSERT(nb11 == sizeof(float)*ne10); | |
GGML_ASSERT(nb1 == sizeof(float)*ne0); | |
src1_row.ne[1] = num_src1_rows; | |
src1_row.nb[1] = nb11; | |
src1_row.nb[2] = num_src1_rows*nb11; | |
src1_row.nb[3] = num_src1_rows*nb11; | |
dst_row.ne[1] = num_src1_rows; | |
dst_row.nb[1] = nb1; | |
dst_row.nb[2] = num_src1_rows*nb1; | |
dst_row.nb[3] = num_src1_rows*nb1; | |
ggml_cuda_mul_mat(ctx, &src0_row, &src1_row, &dst_row); | |
{ | |
dim3 block_dims(std::min((unsigned int)ne0, 768u)); | |
dim3 grid_dims(num_src1_rows); | |
k_copy_dst_from_contiguous<<<grid_dims, block_dims, 0, stream>>>( | |
dst_original, dst_contiguous.get(), | |
dev_row_mapping.get(), | |
ne0, | |
nb1, nb2); | |
CUDA_CHECK(cudaGetLastError()); | |
} | |
} | |
} | |
} | |
static bool ggml_cuda_compute_forward(ggml_backend_cuda_context & ctx, struct ggml_tensor * dst) { | |
// why is this here instead of mul_mat? | |
if (dst->src[0] != nullptr && ggml_backend_buft_is_cuda_split(dst->src[0]->buffer->buft)) { | |
ggml_cuda_set_peer_access(dst->src[1]->ne[1], ctx.device); | |
} | |
switch (dst->op) { | |
case GGML_OP_ARGMAX: | |
ggml_cuda_argmax(ctx, dst); | |
break; | |
case GGML_OP_COUNT_EQUAL: | |
ggml_cuda_count_equal(ctx, dst); | |
break; | |
case GGML_OP_REPEAT: | |
ggml_cuda_op_repeat(ctx, dst); | |
break; | |
case GGML_OP_REPEAT_BACK: | |
ggml_cuda_op_repeat_back(ctx, dst); | |
break; | |
case GGML_OP_GET_ROWS: | |
ggml_cuda_op_get_rows(ctx, dst); | |
break; | |
case GGML_OP_DUP: | |
ggml_cuda_dup(ctx, dst); | |
break; | |
case GGML_OP_CPY: | |
ggml_cuda_cpy(ctx, dst->src[0], dst->src[1]); | |
break; | |
case GGML_OP_CONT: | |
ggml_cuda_dup(ctx, dst); | |
break; | |
case GGML_OP_ADD: | |
case GGML_OP_ADD1: // TODO: more efficient implementation | |
ggml_cuda_op_add(ctx, dst); | |
break; | |
case GGML_OP_SUB: | |
ggml_cuda_op_sub(ctx, dst); | |
break; | |
case GGML_OP_ACC: | |
ggml_cuda_op_acc(ctx, dst); | |
break; | |
case GGML_OP_MUL: | |
ggml_cuda_op_mul(ctx, dst); | |
break; | |
case GGML_OP_DIV: | |
ggml_cuda_op_div(ctx, dst); | |
break; | |
case GGML_OP_UNARY: | |
switch (ggml_get_unary_op(dst)) { | |
case GGML_UNARY_OP_NEG: | |
ggml_cuda_op_neg(ctx, dst); | |
break; | |
case GGML_UNARY_OP_STEP: | |
ggml_cuda_op_step(ctx, dst); | |
break; | |
case GGML_UNARY_OP_GELU: | |
ggml_cuda_op_gelu(ctx, dst); | |
break; | |
case GGML_UNARY_OP_SILU: | |
ggml_cuda_op_silu(ctx, dst); | |
break; | |
case GGML_UNARY_OP_GELU_QUICK: | |
ggml_cuda_op_gelu_quick(ctx, dst); | |
break; | |
case GGML_UNARY_OP_TANH: | |
ggml_cuda_op_tanh(ctx, dst); | |
break; | |
case GGML_UNARY_OP_RELU: | |
ggml_cuda_op_relu(ctx, dst); | |
break; | |
case GGML_UNARY_OP_SIGMOID: | |
ggml_cuda_op_sigmoid(ctx, dst); | |
break; | |
case GGML_UNARY_OP_HARDSIGMOID: | |
ggml_cuda_op_hardsigmoid(ctx, dst); | |
break; | |
case GGML_UNARY_OP_HARDSWISH: | |
ggml_cuda_op_hardswish(ctx, dst); | |
break; | |
case GGML_UNARY_OP_EXP: | |
ggml_cuda_op_exp(ctx, dst); | |
break; | |
default: | |
return false; | |
} | |
break; | |
case GGML_OP_NORM: | |
ggml_cuda_op_norm(ctx, dst); | |
break; | |
case GGML_OP_GROUP_NORM: | |
ggml_cuda_op_group_norm(ctx, dst); | |
break; | |
case GGML_OP_CONCAT: | |
ggml_cuda_op_concat(ctx, dst); | |
break; | |
case GGML_OP_UPSCALE: | |
ggml_cuda_op_upscale(ctx, dst); | |
break; | |
case GGML_OP_PAD: | |
ggml_cuda_op_pad(ctx, dst); | |
break; | |
case GGML_OP_ARANGE: | |
ggml_cuda_op_arange(ctx, dst); | |
break; | |
case GGML_OP_TIMESTEP_EMBEDDING: | |
ggml_cuda_op_timestep_embedding(ctx, dst); | |
break; | |
case GGML_OP_LEAKY_RELU: | |
ggml_cuda_op_leaky_relu(ctx, dst); | |
break; | |
case GGML_OP_RMS_NORM: | |
ggml_cuda_op_rms_norm(ctx, dst); | |
break; | |
case GGML_OP_MUL_MAT: | |
if (dst->src[0]->ne[3] != dst->src[1]->ne[3]) { | |
GGML_LOG_ERROR("%s: cannot compute %s: src0->ne[3] = %" PRId64 ", src1->ne[3] = %" PRId64 " - fallback to CPU\n", __func__, dst->name, dst->src[0]->ne[3], dst->src[1]->ne[3]); | |
return false; | |
} else { | |
ggml_cuda_mul_mat(ctx, dst->src[0], dst->src[1], dst); | |
} | |
break; | |
case GGML_OP_MUL_MAT_ID: | |
ggml_cuda_mul_mat_id(ctx, dst); | |
break; | |
case GGML_OP_OUT_PROD: | |
ggml_cuda_out_prod(ctx, dst); | |
break; | |
case GGML_OP_SCALE: | |
ggml_cuda_op_scale(ctx, dst); | |
break; | |
case GGML_OP_SQR: | |
ggml_cuda_op_sqr(ctx, dst); | |
break; | |
case GGML_OP_SQRT: | |
ggml_cuda_op_sqrt(ctx, dst); | |
break; | |
case GGML_OP_SIN: | |
ggml_cuda_op_sin(ctx, dst); | |
break; | |
case GGML_OP_COS: | |
ggml_cuda_op_cos(ctx, dst); | |
break; | |
case GGML_OP_CLAMP: | |
ggml_cuda_op_clamp(ctx, dst); | |
break; | |
case GGML_OP_NONE: | |
case GGML_OP_RESHAPE: | |
case GGML_OP_VIEW: | |
case GGML_OP_PERMUTE: | |
case GGML_OP_TRANSPOSE: | |
break; | |
case GGML_OP_DIAG_MASK_INF: | |
ggml_cuda_op_diag_mask_inf(ctx, dst); | |
break; | |
case GGML_OP_SOFT_MAX: | |
ggml_cuda_op_soft_max(ctx, dst); | |
break; | |
case GGML_OP_ROPE: | |
ggml_cuda_op_rope(ctx, dst); | |
break; | |
case GGML_OP_IM2COL: | |
ggml_cuda_op_im2col(ctx, dst); | |
break; | |
case GGML_OP_CONV_TRANSPOSE_1D: | |
ggml_cuda_op_conv_transpose_1d(ctx,dst); | |
break; | |
case GGML_OP_POOL_2D: | |
ggml_cuda_op_pool2d(ctx, dst); | |
break; | |
case GGML_OP_SUM: | |
ggml_cuda_op_sum(ctx, dst); | |
break; | |
case GGML_OP_SUM_ROWS: | |
ggml_cuda_op_sum_rows(ctx, dst); | |
break; | |
case GGML_OP_ARGSORT: | |
ggml_cuda_op_argsort(ctx, dst); | |
break; | |
case GGML_OP_FLASH_ATTN_EXT: | |
ggml_cuda_flash_attn_ext(ctx, dst); | |
break; | |
case GGML_OP_CROSS_ENTROPY_LOSS: | |
ggml_cuda_cross_entropy_loss(ctx, dst); | |
break; | |
case GGML_OP_RWKV_WKV: | |
ggml_cuda_op_rwkv_wkv(ctx, dst); | |
break; | |
case GGML_OP_CROSS_ENTROPY_LOSS_BACK: | |
ggml_cuda_cross_entropy_loss_back(ctx, dst); | |
break; | |
case GGML_OP_OPT_STEP_ADAMW: | |
ggml_cuda_opt_step_adamw(ctx, dst); | |
break; | |
default: | |
return false; | |
} | |
cudaError_t err = cudaGetLastError(); | |
if (err != cudaSuccess) { | |
GGML_LOG_ERROR("%s: %s failed\n", __func__, ggml_op_desc(dst)); | |
CUDA_CHECK(err); | |
} | |
return true; | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
// backend | |
static const char * ggml_backend_cuda_get_name(ggml_backend_t backend) { | |
ggml_backend_cuda_context * cuda_ctx = (ggml_backend_cuda_context *)backend->context; | |
return cuda_ctx->name.c_str(); | |
} | |
static void ggml_backend_cuda_free(ggml_backend_t backend) { | |
ggml_backend_cuda_context * cuda_ctx = (ggml_backend_cuda_context *)backend->context; | |
delete cuda_ctx; | |
delete backend; | |
} | |
static void ggml_backend_cuda_set_tensor_async(ggml_backend_t backend, ggml_tensor * tensor, const void * data, size_t offset, size_t size) { | |
ggml_backend_cuda_context * cuda_ctx = (ggml_backend_cuda_context *)backend->context; | |
ggml_backend_buffer_t buf = tensor->view_src ? tensor->view_src->buffer : tensor->buffer; | |
GGML_ASSERT(buf->buft == ggml_backend_cuda_buffer_type(cuda_ctx->device) && "unsupported buffer type"); | |
CUDA_CHECK(cudaMemcpyAsync((char *)tensor->data + offset, data, size, cudaMemcpyHostToDevice, cuda_ctx->stream())); | |
} | |
static void ggml_backend_cuda_get_tensor_async(ggml_backend_t backend, const ggml_tensor * tensor, void * data, size_t offset, size_t size) { | |
ggml_backend_cuda_context * cuda_ctx = (ggml_backend_cuda_context *)backend->context; | |
ggml_backend_buffer_t buf = tensor->view_src ? tensor->view_src->buffer : tensor->buffer; | |
GGML_ASSERT(buf->buft == ggml_backend_cuda_buffer_type(cuda_ctx->device) && "unsupported buffer type"); | |
CUDA_CHECK(cudaMemcpyAsync(data, (const char *)tensor->data + offset, size, cudaMemcpyDeviceToHost, cuda_ctx->stream())); | |
} | |
static bool ggml_backend_cuda_cpy_tensor_async(ggml_backend_t backend_src, ggml_backend_t backend_dst, const ggml_tensor * src, ggml_tensor * dst) { | |
ggml_backend_buffer_t buf_src = src->view_src ? src->view_src->buffer : src->buffer; | |
ggml_backend_buffer_t buf_dst = dst->view_src ? dst->view_src->buffer : dst->buffer; | |
if (!ggml_backend_is_cuda(backend_src) || !ggml_backend_is_cuda(backend_dst)) { | |
return false; | |
} | |
if (!ggml_backend_buffer_is_cuda(src->buffer) || !ggml_backend_buffer_is_cuda(dst->buffer)) { | |
return false; | |
} | |
// device -> device copy | |
ggml_backend_cuda_context * cuda_ctx_src = (ggml_backend_cuda_context *)backend_src->context; | |
ggml_backend_cuda_context * cuda_ctx_dst = (ggml_backend_cuda_context *)backend_dst->context; | |
ggml_backend_cuda_buffer_context * buf_ctx_src = (ggml_backend_cuda_buffer_context *)buf_src->context; | |
ggml_backend_cuda_buffer_context * buf_ctx_dst = (ggml_backend_cuda_buffer_context *)buf_dst->context; | |
if (cuda_ctx_src->device != buf_ctx_src->device || cuda_ctx_dst->device != buf_ctx_dst->device) { | |
#ifndef NDEBUG | |
GGML_LOG_DEBUG("%s: backend and buffer devices do not match\n", __func__); | |
#endif | |
return false; | |
} | |
if (backend_src != backend_dst) { | |
// copy on src stream | |
if (cuda_ctx_src->device == cuda_ctx_dst->device) { | |
CUDA_CHECK(cudaMemcpyAsync(dst->data, src->data, ggml_nbytes(dst), cudaMemcpyDeviceToDevice, cuda_ctx_src->stream())); | |
} else { | |
#ifdef GGML_CUDA_NO_PEER_COPY | |
return false; | |
#else | |
CUDA_CHECK(cudaMemcpyPeerAsync(dst->data, cuda_ctx_dst->device, src->data, cuda_ctx_src->device, ggml_nbytes(dst), cuda_ctx_src->stream())); | |
#endif | |
} | |
// record event on src stream after the copy | |
if (!cuda_ctx_src->copy_event) { | |
ggml_cuda_set_device(cuda_ctx_src->device); | |
CUDA_CHECK(cudaEventCreateWithFlags(&cuda_ctx_src->copy_event, cudaEventDisableTiming)); | |
} | |
CUDA_CHECK(cudaEventRecord(cuda_ctx_src->copy_event, cuda_ctx_src->stream())); | |
// wait on dst stream for the copy to complete | |
CUDA_CHECK(cudaStreamWaitEvent(cuda_ctx_dst->stream(), cuda_ctx_src->copy_event, 0)); | |
} else { | |
// src and dst are on the same backend | |
CUDA_CHECK(cudaMemcpyAsync(dst->data, src->data, ggml_nbytes(dst), cudaMemcpyDeviceToDevice, cuda_ctx_src->stream())); | |
} | |
return true; | |
} | |
static void ggml_backend_cuda_synchronize(ggml_backend_t backend) { | |
ggml_backend_cuda_context * cuda_ctx = (ggml_backend_cuda_context *)backend->context; | |
CUDA_CHECK(cudaStreamSynchronize(cuda_ctx->stream())); | |
GGML_UNUSED(backend); | |
} | |
#ifdef USE_CUDA_GRAPH | |
static void set_ggml_graph_node_properties(ggml_tensor * node, ggml_graph_node_properties * graph_node_properties) { | |
graph_node_properties->node_address = node->data; | |
graph_node_properties->node_op = node->op; | |
for (int i = 0; i < GGML_MAX_DIMS; i++) { | |
graph_node_properties->ne[i] = node->ne[i]; | |
graph_node_properties->nb[i] = node->nb[i]; | |
} | |
for (int i = 0; i < GGML_MAX_SRC; i++) { | |
graph_node_properties->src_address[i] = node->src[i] ? node->src[i]->data : nullptr; | |
} | |
memcpy(graph_node_properties->op_params, node->op_params, GGML_MAX_OP_PARAMS); | |
} | |
static bool ggml_graph_node_has_matching_properties(ggml_tensor * node, ggml_graph_node_properties * graph_node_properties) { | |
if (node->data != graph_node_properties->node_address && | |
node->op != GGML_OP_CPY && | |
node->op != GGML_OP_VIEW) { | |
return false; | |
} | |
if (node->op != graph_node_properties->node_op) { | |
return false; | |
} | |
for (int i = 0; i < GGML_MAX_DIMS; i++) { | |
if (node->ne[i] != graph_node_properties->ne[i]) { | |
return false; | |
} | |
if (node->nb[i] != graph_node_properties->nb[i]) { | |
return false; | |
} | |
} | |
for (int i = 0; i < GGML_MAX_SRC; i++) { | |
if (node->src[i] && | |
node->src[i]->data != graph_node_properties->src_address[i] && | |
node->op != GGML_OP_CPY && | |
node->op != GGML_OP_VIEW | |
) { | |
return false; | |
} | |
} | |
if (node->op == GGML_OP_SCALE && | |
memcmp(graph_node_properties->op_params, node->op_params, GGML_MAX_OP_PARAMS) != 0) { | |
return false; | |
} | |
return true; | |
} | |
#endif | |
static enum ggml_status ggml_backend_cuda_graph_compute(ggml_backend_t backend, ggml_cgraph * cgraph) { | |
ggml_backend_cuda_context * cuda_ctx = (ggml_backend_cuda_context *)backend->context; | |
ggml_cuda_set_device(cuda_ctx->device); | |
#ifdef USE_CUDA_GRAPH | |
static const bool disable_cuda_graphs_due_to_env = (getenv("GGML_CUDA_DISABLE_GRAPHS") != nullptr); | |
// Objects required for CUDA Graph | |
if (cuda_ctx->cuda_graph == nullptr) { | |
cuda_ctx->cuda_graph.reset(new ggml_cuda_graph()); | |
} | |
bool use_cuda_graph = true; | |
bool cuda_graph_update_required = false; | |
// vector of pointers to CUDA cpy kernels, which are required to identify | |
// kernel parameters which need updated in the graph for each token | |
std::vector<void *> ggml_cuda_cpy_fn_ptrs; | |
if (cuda_ctx->cuda_graph->graph == nullptr) { | |
if (ggml_cuda_info().devices[cuda_ctx->device].cc < CC_AMPERE) { | |
cuda_ctx->cuda_graph->disable_due_to_gpu_arch = true; | |
#ifndef NDEBUG | |
GGML_LOG_DEBUG("%s: disabling CUDA graphs due to GPU architecture\n", __func__); | |
#endif | |
} | |
} | |
// Disable CUDA graphs in presence of env var, old GPU, use-case which is changing too rapidly, | |
// or previous graph capture failure. | |
// Also disable for multi-gpu for now. TO DO investigate | |
if (disable_cuda_graphs_due_to_env | |
|| cuda_ctx->cuda_graph->disable_due_to_gpu_arch | |
|| cuda_ctx->cuda_graph->disable_due_to_too_many_updates | |
|| cuda_ctx->cuda_graph->disable_due_to_failed_graph_capture) { | |
use_cuda_graph = false; | |
} | |
if (use_cuda_graph) { | |
if (cuda_ctx->cuda_graph->instance == nullptr) { | |
cuda_graph_update_required = true; | |
} | |
// Check if the graph size has changed | |
if (cuda_ctx->cuda_graph->ggml_graph_properties.size() != (size_t)cgraph->n_nodes) { | |
cuda_graph_update_required = true; | |
cuda_ctx->cuda_graph->ggml_graph_properties.resize(cgraph->n_nodes); | |
} | |
// Loop over nodes in GGML graph to determine if CUDA graph update is required | |
// and store properties to allow this comparison for the next token | |
for (int i = 0; i < cgraph->n_nodes; i++) { | |
bool has_matching_properties = true; | |
if (!cuda_graph_update_required) { | |
has_matching_properties = ggml_graph_node_has_matching_properties(cgraph->nodes[i], &cuda_ctx->cuda_graph->ggml_graph_properties[i]); | |
} | |
if (!has_matching_properties) { | |
cuda_graph_update_required = true; | |
} | |
set_ggml_graph_node_properties(cgraph->nodes[i], &cuda_ctx->cuda_graph->ggml_graph_properties[i]); | |
} | |
// Loop over nodes in GGML graph to obtain info needed for CUDA graph | |
cuda_ctx->cuda_graph->updated_kernel_arg.clear(); | |
for (int i = 0; i < cgraph->n_nodes; i++) { | |
ggml_tensor * node = cgraph->nodes[i]; | |
if (ggml_is_empty(node) || node->op == GGML_OP_RESHAPE || node->op == GGML_OP_TRANSPOSE || node->op == GGML_OP_VIEW || node->op == GGML_OP_PERMUTE || node->op == GGML_OP_NONE) { | |
continue; | |
} | |
if (node->src[0] && node->src[0]->buffer && ggml_backend_buft_is_cuda_split(node->src[0]->buffer->buft)) { | |
use_cuda_graph = false; // Split buffers are not supported by CUDA graph capture | |
#ifndef NDEBUG | |
GGML_LOG_DEBUG("%s: disabling CUDA graphs due to split buffer\n", __func__); | |
#endif | |
} | |
if (node->op == GGML_OP_MUL_MAT_ID) { | |
use_cuda_graph = false; // This node type is not supported by CUDA graph capture | |
#ifndef NDEBUG | |
GGML_LOG_DEBUG("%s: disabling CUDA graphs due to mul_mat_id\n", __func__); | |
#endif | |
} | |
if (node->op == GGML_OP_ADD && node->src[1] && node->src[1]->ne[1] > 1) { | |
// disable CUDA graphs for batch size > 1 for now. | |
// Changes in batch size or context size can cause changes to the grid size of some kernels. | |
use_cuda_graph = false; | |
#ifndef NDEBUG | |
GGML_LOG_DEBUG("%s: disabling CUDA graphs due to batch size > 1 [%s] [%ld %ld %ld %ld]\n", __func__, node->name, node->ne[0], node->ne[1], node->ne[2], node->ne[3]); | |
#endif | |
} | |
if (node->op == GGML_OP_CPY) { | |
// store the copy op parameter which changes with each token. | |
cuda_ctx->cuda_graph->updated_kernel_arg.push_back((char **) &(node->src[1]->data)); | |
// store a pointer to each copy op CUDA kernel to identify it later | |
void * ptr = ggml_cuda_cpy_fn(node->src[0], node->src[1]); | |
if (!ptr) { | |
use_cuda_graph = false; | |
#ifndef NDEBUG | |
GGML_LOG_DEBUG("%s: disabling CUDA graphs due to unsupported copy op\n", __func__); | |
#endif | |
} else { | |
if (std::find(ggml_cuda_cpy_fn_ptrs.begin(), ggml_cuda_cpy_fn_ptrs.end(), ptr) == ggml_cuda_cpy_fn_ptrs.end()) { | |
ggml_cuda_cpy_fn_ptrs.push_back(ptr); | |
} | |
} | |
} | |
if (!use_cuda_graph) { | |
break; | |
} | |
} | |
// Disable CUDA graphs (from the next token) if the use-case is demanding too many consecutive graph updates. | |
if (use_cuda_graph && cuda_graph_update_required) { | |
cuda_ctx->cuda_graph->number_consecutive_updates++; | |
} else { | |
cuda_ctx->cuda_graph->number_consecutive_updates = 0; | |
} | |
if (cuda_ctx->cuda_graph->number_consecutive_updates >= 4) { | |
cuda_ctx->cuda_graph->disable_due_to_too_many_updates = true; | |
#ifndef NDEBUG | |
GGML_LOG_DEBUG("%s: disabling CUDA graphs due to too many consecutive updates\n", __func__); | |
#endif | |
} | |
} | |
if (use_cuda_graph && cuda_graph_update_required) { // Start CUDA graph capture | |
CUDA_CHECK(cudaStreamBeginCapture(cuda_ctx->stream(), cudaStreamCaptureModeRelaxed)); | |
} | |
#else | |
bool use_cuda_graph = false; | |
bool cuda_graph_update_required = false; | |
#endif // USE_CUDA_GRAPH | |
bool graph_evaluated_or_captured = false; | |
while (!graph_evaluated_or_captured) { | |
// Only perform the graph execution if CUDA graphs are not enabled, or we are capturing the graph. | |
// With the use of CUDA graphs, the execution will be performed by the graph launch. | |
if (!use_cuda_graph || cuda_graph_update_required) { | |
for (int i = 0; i < cgraph->n_nodes; i++) { | |
ggml_tensor * node = cgraph->nodes[i]; | |
if (ggml_is_empty(node) || node->op == GGML_OP_RESHAPE || node->op == GGML_OP_TRANSPOSE || node->op == GGML_OP_VIEW || node->op == GGML_OP_PERMUTE || node->op == GGML_OP_NONE) { | |
continue; | |
} | |
#ifndef NDEBUG | |
assert(node->buffer->buft == ggml_backend_cuda_buffer_type(cuda_ctx->device)); | |
for (int j = 0; j < GGML_MAX_SRC; j++) { | |
if (node->src[j] != nullptr) { | |
assert(node->src[j]->buffer); | |
assert(node->src[j]->buffer->buft == ggml_backend_cuda_buffer_type(cuda_ctx->device) || | |
ggml_backend_buft_is_cuda_split(node->src[j]->buffer->buft)); | |
} | |
} | |
#endif | |
bool ok = ggml_cuda_compute_forward(*cuda_ctx, node); | |
if (!ok) { | |
GGML_LOG_ERROR("%s: op not supported %s (%s)\n", __func__, node->name, ggml_op_name(node->op)); | |
} | |
GGML_ASSERT(ok); | |
} | |
} | |
#ifdef USE_CUDA_GRAPH | |
if (use_cuda_graph && cuda_graph_update_required) { // End CUDA graph capture | |
if (cuda_ctx->cuda_graph->graph != nullptr) { | |
CUDA_CHECK(cudaGraphDestroy(cuda_ctx->cuda_graph->graph)); | |
cuda_ctx->cuda_graph->graph = nullptr; | |
} | |
CUDA_CHECK(cudaStreamEndCapture(cuda_ctx->stream(), &cuda_ctx->cuda_graph->graph)); | |
#if 0 | |
if (disable_cuda_graphs_due_to_failed_capture) { | |
use_cuda_graph = false; | |
cuda_ctx->cuda_graph->disable_due_to_failed_graph_capture = true; | |
#ifndef NDEBUG | |
GGML_LOG_DEBUG("%s: disabling CUDA graphs due to failed graph capture\n", __func__); | |
#endif | |
} else { | |
graph_evaluated_or_captured = true; // CUDA graph has been captured | |
} | |
#endif | |
graph_evaluated_or_captured = true; // CUDA graph has been captured | |
} else { | |
graph_evaluated_or_captured = true; // ggml graph has been directly evaluated | |
} | |
} | |
if (use_cuda_graph) { | |
if (cuda_ctx->cuda_graph->instance == nullptr) { // Create executable graph from captured graph. | |
CUDA_CHECK(cudaGraphInstantiate(&cuda_ctx->cuda_graph->instance, cuda_ctx->cuda_graph->graph, NULL, NULL, 0)); | |
} | |
// Perform update to graph (if required for this token), and change copy parameter (required for every token) | |
if (cuda_graph_update_required) { | |
// Extract nodes from graph | |
// First call with null argument gets number of nodes in graph | |
CUDA_CHECK(cudaGraphGetNodes(cuda_ctx->cuda_graph->graph, nullptr, &cuda_ctx->cuda_graph->num_nodes)); | |
// Subsequent call with non-null argument gets nodes | |
cuda_ctx->cuda_graph->nodes.clear(); | |
cuda_ctx->cuda_graph->nodes.resize(cuda_ctx->cuda_graph->num_nodes); | |
cuda_ctx->cuda_graph->params.clear(); | |
cuda_ctx->cuda_graph->params.resize(cuda_ctx->cuda_graph->num_nodes); | |
if (cuda_ctx->cuda_graph->num_nodes > 0) { | |
CUDA_CHECK(cudaGraphGetNodes(cuda_ctx->cuda_graph->graph, cuda_ctx->cuda_graph->nodes.data(), &cuda_ctx->cuda_graph->num_nodes)); | |
// Loop over nodes, and extract kernel parameters from each node | |
for (size_t i = 0; i < cuda_ctx->cuda_graph->num_nodes; i++) { | |
cudaGraphNodeType node_type; | |
CUDA_CHECK(cudaGraphNodeGetType(cuda_ctx->cuda_graph->nodes[i], &node_type)); | |
if (node_type == cudaGraphNodeTypeKernel) { | |
cudaError_t stat = cudaGraphKernelNodeGetParams(cuda_ctx->cuda_graph->nodes[i], &cuda_ctx->cuda_graph->params[i]); // Get params using runtime | |
if (stat == cudaErrorInvalidDeviceFunction) { | |
// Fails due to incorrect handling by CUDA runtime of CUDA BLAS node. | |
// We don't need to update blas nodes, so clear error and move on. | |
cudaGetLastError(); | |
} else { | |
GGML_ASSERT(stat == cudaSuccess); | |
} | |
} | |
} | |
} | |
} | |
// One of the arguments to the copy kernel is updated for each token, hence we need to | |
// replace that argument with the updated value in the CUDA graph | |
if (!cuda_graph_update_required) { // on update steps, the live parameters will already be captured | |
int k = 0; | |
for (size_t i = 0; i < cuda_ctx->cuda_graph->num_nodes; i++) { | |
if(count(ggml_cuda_cpy_fn_ptrs.begin(), ggml_cuda_cpy_fn_ptrs.end(), cuda_ctx->cuda_graph->params[i].func) > 0) { | |
char ** updated_kernel_arg_ptr = cuda_ctx->cuda_graph->updated_kernel_arg.at(k++); | |
cuda_ctx->cuda_graph->params[i].kernelParams[1] = updated_kernel_arg_ptr; | |
CUDA_CHECK(cudaGraphKernelNodeSetParams(cuda_ctx->cuda_graph->nodes[i], &cuda_ctx->cuda_graph->params[i])); | |
} | |
} | |
} | |
// Update graph executable | |
cudaGraphExecUpdateResultInfo result_info; | |
cudaError_t stat = cudaGraphExecUpdate(cuda_ctx->cuda_graph->instance, cuda_ctx->cuda_graph->graph, &result_info); | |
if (stat == cudaErrorGraphExecUpdateFailure) { | |
#ifndef NDEBUG | |
GGML_LOG_DEBUG("%s: CUDA graph update failed\n", __func__); | |
#endif | |
// The pre-existing graph exec cannot be updated due to violated constraints | |
// so instead clear error and re-instantiate | |
cudaGetLastError(); | |
CUDA_CHECK(cudaGraphExecDestroy(cuda_ctx->cuda_graph->instance)); | |
cuda_ctx->cuda_graph->instance = nullptr; | |
CUDA_CHECK(cudaGraphInstantiate(&cuda_ctx->cuda_graph->instance, cuda_ctx->cuda_graph->graph, NULL, NULL, 0)); | |
} else { | |
GGML_ASSERT(stat == cudaSuccess); | |
} | |
// Launch graph | |
CUDA_CHECK(cudaGraphLaunch(cuda_ctx->cuda_graph->instance, cuda_ctx->stream())); | |
#else | |
graph_evaluated_or_captured = true; | |
#endif // USE_CUDA_GRAPH | |
} | |
return GGML_STATUS_SUCCESS; | |
} | |
static void ggml_backend_cuda_event_record(ggml_backend_t backend, ggml_backend_event_t event) { | |
ggml_backend_cuda_context * cuda_ctx = (ggml_backend_cuda_context *)backend->context; | |
CUDA_CHECK(cudaEventRecord((cudaEvent_t)event->context, cuda_ctx->stream())); | |
} | |
static void ggml_backend_cuda_event_wait(ggml_backend_t backend, ggml_backend_event_t event) { | |
ggml_backend_cuda_context * cuda_ctx = (ggml_backend_cuda_context *)backend->context; | |
if (ggml_backend_is_cuda(backend)) { | |
CUDA_CHECK(cudaStreamWaitEvent(cuda_ctx->stream(), (cudaEvent_t)event->context, 0)); | |
} else { | |
#if 0 | |
// untested | |
auto wait_fn = [](void * user_data) { | |
ggml_backend_event_t event = (ggml_backend_event_t)user_data; | |
ggml_backend_event_synchronize(event); | |
}; | |
CUDA_CHECK(cudaLaunchHostFunc(cuda_ctx->stream(), wait_fn, event)); | |
#endif | |
GGML_ABORT("fatal error"); | |
} | |
} | |
static const ggml_backend_i ggml_backend_cuda_interface = { | |
/* .get_name = */ ggml_backend_cuda_get_name, | |
/* .free = */ ggml_backend_cuda_free, | |
/* .set_tensor_async = */ ggml_backend_cuda_set_tensor_async, | |
/* .get_tensor_async = */ ggml_backend_cuda_get_tensor_async, | |
/* .cpy_tensor_async = */ ggml_backend_cuda_cpy_tensor_async, | |
/* .synchronize = */ ggml_backend_cuda_synchronize, | |
/* .graph_plan_create = */ NULL, | |
/* .graph_plan_free = */ NULL, | |
/* .graph_plan_update = */ NULL, | |
/* .graph_plan_compute = */ NULL, | |
/* .graph_compute = */ ggml_backend_cuda_graph_compute, | |
/* .event_record = */ ggml_backend_cuda_event_record, | |
/* .event_wait = */ ggml_backend_cuda_event_wait, | |
}; | |
static ggml_guid_t ggml_backend_cuda_guid() { | |
static ggml_guid guid = { 0x2c, 0xdd, 0xe8, 0x1c, 0x65, 0xb3, 0x65, 0x73, 0x6a, 0x12, 0x88, 0x61, 0x1c, 0xc9, 0xdc, 0x25 }; | |
return &guid; | |
} | |
bool ggml_backend_is_cuda(ggml_backend_t backend) { | |
return backend != NULL && ggml_guid_matches(backend->guid, ggml_backend_cuda_guid()); | |
} | |
int ggml_backend_cuda_get_device_count() { | |
return ggml_cuda_info().device_count; | |
} | |
void ggml_backend_cuda_get_device_description(int device, char * description, size_t description_size) { | |
cudaDeviceProp prop; | |
CUDA_CHECK(cudaGetDeviceProperties(&prop, device)); | |
snprintf(description, description_size, "%s", prop.name); | |
} | |
void ggml_backend_cuda_get_device_memory(int device, size_t * free, size_t * total) { | |
ggml_cuda_set_device(device); | |
CUDA_CHECK(cudaMemGetInfo(free, total)); | |
} | |
bool ggml_backend_cuda_register_host_buffer(void * buffer, size_t size) { | |
if (getenv("GGML_CUDA_REGISTER_HOST") == nullptr) { | |
return false; | |
} | |
#if CUDART_VERSION >= 11100 || defined(GGML_USE_MUSA) | |
cudaError_t err = cudaHostRegister(buffer, size, cudaHostRegisterPortable | cudaHostRegisterReadOnly); | |
if (err != cudaSuccess) { | |
// clear the error | |
cudaGetLastError(); | |
GGML_LOG_DEBUG("%s: failed to register %.2f MiB of pinned memory: %s\n", __func__, | |
size / 1024.0 / 1024.0, cudaGetErrorString(err)); | |
return false; | |
} | |
return true; | |
#else | |
return false; | |
#endif | |
} | |
void ggml_backend_cuda_unregister_host_buffer(void * buffer) { | |
if (getenv("GGML_CUDA_REGISTER_HOST") == nullptr) { | |
return; | |
} | |
cudaError_t err = cudaHostUnregister(buffer); | |
if (err != cudaSuccess) { | |
// clear the error | |
cudaGetLastError(); | |
} | |
} | |
// backend device | |
struct ggml_backend_cuda_device_context { | |
int device; | |
std::string name; | |
std::string description; | |
}; | |
static const char * ggml_backend_cuda_device_get_name(ggml_backend_dev_t dev) { | |
ggml_backend_cuda_device_context * ctx = (ggml_backend_cuda_device_context *)dev->context; | |
return ctx->name.c_str(); | |
} | |
static const char * ggml_backend_cuda_device_get_description(ggml_backend_dev_t dev) { | |
ggml_backend_cuda_device_context * ctx = (ggml_backend_cuda_device_context *)dev->context; | |
return ctx->description.c_str(); | |
} | |
static void ggml_backend_cuda_device_get_memory(ggml_backend_dev_t dev, size_t * free, size_t * total) { | |
ggml_backend_cuda_device_context * ctx = (ggml_backend_cuda_device_context *)dev->context; | |
ggml_cuda_set_device(ctx->device); | |
CUDA_CHECK(cudaMemGetInfo(free, total)); | |
} | |
static enum ggml_backend_dev_type ggml_backend_cuda_device_get_type(ggml_backend_dev_t dev) { | |
GGML_UNUSED(dev); | |
return GGML_BACKEND_DEVICE_TYPE_GPU; | |
} | |
static void ggml_backend_cuda_device_get_props(ggml_backend_dev_t dev, ggml_backend_dev_props * props) { | |
props->name = ggml_backend_cuda_device_get_name(dev); | |
props->description = ggml_backend_cuda_device_get_description(dev); | |
props->type = ggml_backend_cuda_device_get_type(dev); | |
ggml_backend_cuda_device_get_memory(dev, &props->memory_free, &props->memory_total); | |
bool host_buffer = getenv("GGML_CUDA_NO_PINNED") == nullptr; | |
#ifdef GGML_CUDA_NO_PEER_COPY | |
bool events = false; | |
#else | |
bool events = true; | |
#endif | |
props->caps = { | |
/* .async = */ true, | |
/* .host_buffer = */ host_buffer, | |
/* .buffer_from_host_ptr = */ false, | |
/* .events = */ events, | |
}; | |
} | |
static ggml_backend_t ggml_backend_cuda_device_init_backend(ggml_backend_dev_t dev, const char * params) { | |
GGML_UNUSED(params); | |
ggml_backend_cuda_device_context * ctx = (ggml_backend_cuda_device_context *)dev->context; | |
return ggml_backend_cuda_init(ctx->device); | |
} | |
static ggml_backend_buffer_type_t ggml_backend_cuda_device_get_buffer_type(ggml_backend_dev_t dev) { | |
ggml_backend_cuda_device_context * ctx = (ggml_backend_cuda_device_context *)dev->context; | |
return ggml_backend_cuda_buffer_type(ctx->device); | |
} | |
static ggml_backend_buffer_type_t ggml_backend_cuda_device_get_host_buffer_type(ggml_backend_dev_t dev) { | |
GGML_UNUSED(dev); | |
return ggml_backend_cuda_host_buffer_type(); | |
} | |
// TODO: move these functions here | |
static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const ggml_tensor * op) { | |
ggml_backend_cuda_device_context * dev_ctx = (ggml_backend_cuda_device_context *) dev->context; | |
// split buffers can only be used with GGML_OP_MUL_MAT | |
if (op->op != GGML_OP_MUL_MAT) { | |
for (int i = 0; i < GGML_MAX_SRC; i++) { | |
if (op->src[i] && op->src[i]->buffer && ggml_backend_buft_is_cuda_split(op->src[i]->buffer->buft)) { | |
return false; | |
} | |
} | |
} | |
// check if all the sources are allocated on this device | |
for (int i = 0; i < GGML_MAX_SRC; i++) { | |
if (op->src[i] && op->src[i]->buffer && ggml_backend_buft_is_cuda(op->src[i]->buffer->buft)) { | |
ggml_backend_cuda_buffer_type_context * buft_ctx = (ggml_backend_cuda_buffer_type_context *)op->src[i]->buffer->buft->context; | |
if (buft_ctx->device != dev_ctx->device) { | |
return false; | |
} | |
} | |
} | |
switch (op->op) { | |
case GGML_OP_UNARY: | |
switch (ggml_get_unary_op(op)) { | |
case GGML_UNARY_OP_NEG: | |
case GGML_UNARY_OP_STEP: | |
case GGML_UNARY_OP_GELU: | |
case GGML_UNARY_OP_SILU: | |
case GGML_UNARY_OP_RELU: | |
case GGML_UNARY_OP_SIGMOID: | |
case GGML_UNARY_OP_HARDSIGMOID: | |
case GGML_UNARY_OP_HARDSWISH: | |
case GGML_UNARY_OP_GELU_QUICK: | |
case GGML_UNARY_OP_TANH: | |
case GGML_UNARY_OP_EXP: | |
return ggml_is_contiguous(op->src[0]); | |
default: | |
return false; | |
} | |
break; | |
case GGML_OP_MUL_MAT: | |
case GGML_OP_MUL_MAT_ID: | |
{ | |
struct ggml_tensor * a = op->src[0]; | |
struct ggml_tensor * b = op->src[1]; | |
if (b->type == GGML_TYPE_F16 && a->type != GGML_TYPE_F16) { | |
return false; | |
} | |
if (op->op == GGML_OP_MUL_MAT && a->ne[3] != b->ne[3]) { | |
return false; | |
} | |
#ifdef GGML_USE_MUSA | |
if (b->type == GGML_TYPE_F16 && b->ne[2]*b->ne[3] > 1 && | |
!ggml_is_transposed(a) && !ggml_is_transposed(b)) { | |
return false; | |
} | |
#endif // GGML_USE_MUSA | |
switch (a->type) { | |
case GGML_TYPE_F32: | |
case GGML_TYPE_F16: | |
case GGML_TYPE_Q4_0: | |
case GGML_TYPE_Q4_1: | |
case GGML_TYPE_Q5_0: | |
case GGML_TYPE_Q5_1: | |
case GGML_TYPE_Q8_0: | |
case GGML_TYPE_Q2_K: | |
case GGML_TYPE_Q3_K: | |
case GGML_TYPE_Q4_K: | |
case GGML_TYPE_Q5_K: | |
case GGML_TYPE_Q6_K: | |
case GGML_TYPE_Q8_K: | |
case GGML_TYPE_IQ1_M: | |
case GGML_TYPE_IQ1_S: | |
case GGML_TYPE_IQ2_S: | |
case GGML_TYPE_IQ2_XS: | |
case GGML_TYPE_IQ2_XXS: | |
case GGML_TYPE_IQ3_S: | |
case GGML_TYPE_IQ3_XXS: | |
case GGML_TYPE_IQ4_NL: | |
case GGML_TYPE_IQ4_XS: | |
#ifdef GGML_USE_MUSA | |
if (a->type == GGML_TYPE_Q3_K) { | |
return false; | |
} | |
#endif // GGML_USE_MUSA | |
return true; | |
default: | |
return false; | |
} | |
} break; | |
case GGML_OP_OUT_PROD: | |
return op->type == GGML_TYPE_F32 && op->src[0]->type == GGML_TYPE_F32 && op->src[1]->type == GGML_TYPE_F32 && op->ne[2] == 1 && op->ne[3] == 1; | |
case GGML_OP_GET_ROWS: | |
{ | |
switch (op->src[0]->type) { | |
case GGML_TYPE_F16: | |
case GGML_TYPE_F32: | |
case GGML_TYPE_Q4_0: | |
case GGML_TYPE_Q4_1: | |
case GGML_TYPE_Q5_0: | |
case GGML_TYPE_Q5_1: | |
case GGML_TYPE_Q8_0: | |
return true; | |
default: | |
return false; | |
} | |
} break; | |
case GGML_OP_CPY: | |
{ | |
ggml_type src0_type = op->src[0]->type; | |
ggml_type src1_type = op->src[1]->type; | |
if (src0_type == GGML_TYPE_F32 && src1_type == GGML_TYPE_F32) { | |
return true; | |
} | |
if (src0_type == GGML_TYPE_F32 && src1_type == GGML_TYPE_F16) { | |
return true; | |
} | |
if (src0_type == GGML_TYPE_F32 && src1_type == GGML_TYPE_Q8_0) { | |
return true; | |
} | |
if (src0_type == GGML_TYPE_Q8_0 && src1_type == GGML_TYPE_F32) { | |
return true; | |
} | |
if (src0_type == GGML_TYPE_F32 && src1_type == GGML_TYPE_Q4_0) { | |
return true; | |
} | |
if (src0_type == GGML_TYPE_F32 && src1_type == GGML_TYPE_Q4_1) { | |
return true; | |
} | |
if (src0_type == GGML_TYPE_F32 && src1_type == GGML_TYPE_Q5_0) { | |
return true; | |
} | |
if (src0_type == GGML_TYPE_F32 && src1_type == GGML_TYPE_Q5_1) { | |
return true; | |
} | |
if (src0_type == GGML_TYPE_F32 && src1_type == GGML_TYPE_IQ4_NL) { | |
return true; | |
} | |
if (src0_type == GGML_TYPE_F16 && src1_type == GGML_TYPE_F16) { | |
return true; | |
} | |
if (src0_type == GGML_TYPE_F16 && src1_type == GGML_TYPE_F32) { | |
return true; | |
} | |
if (src0_type == src1_type && ggml_is_contiguous(op->src[0]) && ggml_is_contiguous(op->src[1])) { | |
return true; | |
} | |
return false; | |
} break; | |
case GGML_OP_DUP: | |
{ | |
ggml_type src0_type = op->src[0]->type; | |
return src0_type != GGML_TYPE_I32 && src0_type != GGML_TYPE_I16; | |
} break; | |
case GGML_OP_ARGMAX: | |
case GGML_OP_COUNT_EQUAL: | |
{ | |
return true; | |
} break; | |
case GGML_OP_REPEAT: | |
{ | |
ggml_type src0_type = op->src[0]->type; | |
return src0_type != GGML_TYPE_I32 && src0_type != GGML_TYPE_I16; | |
} break; | |
case GGML_OP_REPEAT_BACK: | |
return op->type == GGML_TYPE_F32 && op->src[0]->ne[3] == 1; | |
case GGML_OP_CONCAT: | |
{ | |
ggml_type src0_type = op->src[0]->type; | |
return src0_type != GGML_TYPE_I32 && src0_type != GGML_TYPE_I16; | |
} break; | |
case GGML_OP_CONV_TRANSPOSE_1D: | |
{ | |
ggml_type src0_type = op->src[0]->type; | |
ggml_type src1_type = op->src[1]->type; | |
if (src0_type == GGML_TYPE_F32 && src1_type == GGML_TYPE_F32) { | |
return true; | |
} | |
return false; | |
} break; | |
case GGML_OP_NORM: | |
case GGML_OP_RMS_NORM: | |
return ggml_is_contiguous(op->src[0]) && op->ne[0] % WARP_SIZE == 0; | |
break; | |
case GGML_OP_NONE: | |
case GGML_OP_RESHAPE: | |
case GGML_OP_VIEW: | |
case GGML_OP_PERMUTE: | |
case GGML_OP_TRANSPOSE: | |
case GGML_OP_ADD: | |
case GGML_OP_ADD1: | |
case GGML_OP_SUB: | |
case GGML_OP_MUL: | |
case GGML_OP_DIV: | |
case GGML_OP_SCALE: | |
case GGML_OP_SQR: | |
case GGML_OP_SQRT: | |
case GGML_OP_SIN: | |
case GGML_OP_COS: | |
case GGML_OP_CLAMP: | |
return true; | |
case GGML_OP_CONT: | |
return op->src[0]->type != GGML_TYPE_BF16; | |
case GGML_OP_DIAG_MASK_INF: | |
case GGML_OP_SOFT_MAX: | |
return true; | |
case GGML_OP_ROPE: | |
return ggml_is_contiguous(op->src[0]); | |
case GGML_OP_IM2COL: | |
case GGML_OP_POOL_2D: | |
case GGML_OP_SUM: | |
case GGML_OP_SUM_ROWS: | |
case GGML_OP_ARGSORT: | |
case GGML_OP_ACC: | |
case GGML_OP_GROUP_NORM: | |
case GGML_OP_UPSCALE: | |
case GGML_OP_PAD: | |
case GGML_OP_ARANGE: | |
case GGML_OP_TIMESTEP_EMBEDDING: | |
case GGML_OP_LEAKY_RELU: | |
case GGML_OP_RWKV_WKV: | |
return true; | |
case GGML_OP_FLASH_ATTN_EXT: { | |
#ifndef FLASH_ATTN_AVAILABLE | |
return false; | |
#endif | |
if (op->src[0]->ne[0] == 64 && op->src[1]->type == GGML_TYPE_F16) { | |
return true; | |
} | |
if (op->src[0]->ne[0] == 128) { | |
return true; | |
} | |
if (op->src[0]->ne[0] == 256 && op->src[1]->type == GGML_TYPE_F16 && op->src[2]->type == GGML_TYPE_F16) { | |
return true; | |
} | |
const int cc = ggml_cuda_info().devices[dev_ctx->device].cc; | |
return cc >= CC_VOLTA && cc < CC_OFFSET_AMD && op->src[1]->type == GGML_TYPE_F16 && op->src[2]->type == GGML_TYPE_F16; | |
} | |
case GGML_OP_CROSS_ENTROPY_LOSS: | |
case GGML_OP_CROSS_ENTROPY_LOSS_BACK: | |
case GGML_OP_OPT_STEP_ADAMW: | |
return true; | |
default: | |
return false; | |
} | |
} | |
static bool ggml_backend_cuda_device_supports_buft(ggml_backend_dev_t dev, ggml_backend_buffer_type_t buft) { | |
return (ggml_backend_buft_is_cuda(buft) || ggml_backend_buft_is_cuda_split(buft)) && buft->device == dev; | |
} | |
static int64_t get_op_batch_size(const ggml_tensor * op) { | |
switch (op->op) { | |
case GGML_OP_GET_ROWS: | |
return 0; | |
case GGML_OP_MUL_MAT: | |
return op->ne[1]; | |
case GGML_OP_MUL_MAT_ID: | |
case GGML_OP_ROPE: | |
return op->ne[2]; | |
default: | |
return ggml_nrows(op); | |
} | |
} | |
static bool ggml_backend_cuda_device_offload_op(ggml_backend_dev_t dev, const ggml_tensor * op) { | |
const int min_batch_size = 32; | |
return get_op_batch_size(op) >= min_batch_size; | |
GGML_UNUSED(dev); | |
} | |
static ggml_backend_event_t ggml_backend_cuda_device_event_new(ggml_backend_dev_t dev) { | |
#ifdef GGML_CUDA_NO_PEER_COPY | |
return nullptr; | |
#else | |
ggml_backend_cuda_device_context * dev_ctx = (ggml_backend_cuda_device_context *)dev->context; | |
ggml_cuda_set_device(dev_ctx->device); | |
cudaEvent_t event; | |
CUDA_CHECK(cudaEventCreateWithFlags(&event, cudaEventDisableTiming)); | |
return new ggml_backend_event { | |
/* .device = */ dev, | |
/* .context = */ event, | |
}; | |
#endif | |
} | |
static void ggml_backend_cuda_device_event_free(ggml_backend_dev_t dev, ggml_backend_event_t event) { | |
GGML_UNUSED(dev); | |
CUDA_CHECK(cudaEventDestroy((cudaEvent_t)event->context)); | |
delete event; | |
} | |
static void ggml_backend_cuda_device_event_synchronize(ggml_backend_dev_t dev, ggml_backend_event_t event) { | |
GGML_UNUSED(dev); | |
CUDA_CHECK(cudaEventSynchronize((cudaEvent_t)event->context)); | |
} | |
static const ggml_backend_device_i ggml_backend_cuda_device_interface = { | |
/* .get_name = */ ggml_backend_cuda_device_get_name, | |
/* .get_description = */ ggml_backend_cuda_device_get_description, | |
/* .get_memory = */ ggml_backend_cuda_device_get_memory, | |
/* .get_type = */ ggml_backend_cuda_device_get_type, | |
/* .get_props = */ ggml_backend_cuda_device_get_props, | |
/* .init_backend = */ ggml_backend_cuda_device_init_backend, | |
/* .get_buffer_type = */ ggml_backend_cuda_device_get_buffer_type, | |
/* .get_host_buffer_type = */ ggml_backend_cuda_device_get_host_buffer_type, | |
/* .buffer_from_host_ptr = */ NULL, | |
/* .supports_op = */ ggml_backend_cuda_device_supports_op, | |
/* .supports_buft = */ ggml_backend_cuda_device_supports_buft, | |
/* .offload_op = */ ggml_backend_cuda_device_offload_op, | |
/* .event_new = */ ggml_backend_cuda_device_event_new, | |
/* .event_free = */ ggml_backend_cuda_device_event_free, | |
/* .event_synchronize = */ ggml_backend_cuda_device_event_synchronize, | |
}; | |
// backend reg | |
struct ggml_backend_cuda_reg_context { | |
std::vector<ggml_backend_dev_t> devices; | |
}; | |
static const char * ggml_backend_cuda_reg_get_name(ggml_backend_reg_t reg) { | |
GGML_UNUSED(reg); | |
return GGML_CUDA_NAME; | |
} | |
static size_t ggml_backend_cuda_reg_get_device_count(ggml_backend_reg_t reg) { | |
ggml_backend_cuda_reg_context * ctx = (ggml_backend_cuda_reg_context *)reg->context; | |
return ctx->devices.size(); | |
} | |
static ggml_backend_dev_t ggml_backend_cuda_reg_get_device(ggml_backend_reg_t reg, size_t index) { | |
ggml_backend_cuda_reg_context * ctx = (ggml_backend_cuda_reg_context *)reg->context; | |
GGML_ASSERT(index < ctx->devices.size()); | |
return ctx->devices[index]; | |
} | |
static void * ggml_backend_cuda_reg_get_proc_address(ggml_backend_reg_t reg, const char * name) { | |
GGML_UNUSED(reg); | |
if (strcmp(name, "ggml_backend_split_buffer_type") == 0) { | |
return (void *)ggml_backend_cuda_split_buffer_type; | |
} | |
if (strcmp(name, "ggml_backend_register_host_buffer") == 0) { | |
return (void *)ggml_backend_cuda_register_host_buffer; | |
} | |
if (strcmp(name, "ggml_backend_unregister_host_buffer") == 0) { | |
return (void *)ggml_backend_cuda_unregister_host_buffer; | |
} | |
return nullptr; | |
} | |
static const ggml_backend_reg_i ggml_backend_cuda_reg_interface = { | |
/* .get_name = */ ggml_backend_cuda_reg_get_name, | |
/* .get_device_count = */ ggml_backend_cuda_reg_get_device_count, | |
/* .get_device_get = */ ggml_backend_cuda_reg_get_device, | |
/* .get_proc_address = */ ggml_backend_cuda_reg_get_proc_address, | |
}; | |
// backend registry | |
ggml_backend_reg_t ggml_backend_cuda_reg() { | |
static ggml_backend_reg reg; | |
static bool initialized = false; | |
{ | |
static std::mutex mutex; | |
std::lock_guard<std::mutex> lock(mutex); | |
if (!initialized) { | |
ggml_backend_cuda_reg_context * ctx = new ggml_backend_cuda_reg_context; | |
for (int i = 0; i < ggml_cuda_info().device_count; i++) { | |
ggml_backend_cuda_device_context * dev_ctx = new ggml_backend_cuda_device_context; | |
dev_ctx->device = i; | |
dev_ctx->name = GGML_CUDA_NAME + std::to_string(i); | |
ggml_cuda_set_device(i); | |
cudaDeviceProp prop; | |
CUDA_CHECK(cudaGetDeviceProperties(&prop, i)); | |
dev_ctx->description = prop.name; | |
ggml_backend_dev_t dev = new ggml_backend_device { | |
/* .interface = */ ggml_backend_cuda_device_interface, | |
/* .reg = */ ®, | |
/* .context = */ dev_ctx | |
}; | |
ctx->devices.push_back(dev); | |
} | |
reg = ggml_backend_reg { | |
/* .interface = */ ggml_backend_cuda_reg_interface, | |
/* .context = */ ctx | |
}; | |
} | |
initialized = true; | |
} | |
return ® | |
} | |
ggml_backend_t ggml_backend_cuda_init(int device) { | |
if (device < 0 || device >= ggml_backend_cuda_get_device_count()) { | |
GGML_LOG_ERROR("%s: invalid device %d\n", __func__, device); | |
return nullptr; | |
} | |
ggml_backend_cuda_context * ctx = new ggml_backend_cuda_context(device); | |
if (ctx == nullptr) { | |
GGML_LOG_ERROR("%s: failed to allocate context\n", __func__); | |
return nullptr; | |
} | |
ggml_backend_t cuda_backend = new ggml_backend { | |
/* .guid = */ ggml_backend_cuda_guid(), | |
/* .interface = */ ggml_backend_cuda_interface, | |
/* .device = */ ggml_backend_reg_dev_get(ggml_backend_cuda_reg(), device), | |
/* .context = */ ctx, | |
}; | |
return cuda_backend; | |
} | |