R/vulkan.R

Defines functions ggml_vulkan_status ggml_vulkan_device_caps ggml_vulkan_backend_name ggml_vulkan_is_backend ggml_vulkan_free ggml_vulkan_init ggml_vulkan_split_buffer_type ggml_vulkan_hard_exit_available ggml_vulkan_shutdown ggml_vulkan_split_mul_mat ggml_vulkan_p2p_selftest ggml_vulkan_split_row_ranges ggml_vulkan_device_groups ggml_vulkan_device_memory ggml_vulkan_device_description ggml_vulkan_list_devices ggml_vulkan_device_count ggml_vulkan_available

Documented in ggml_vulkan_available ggml_vulkan_backend_name ggml_vulkan_device_caps ggml_vulkan_device_count ggml_vulkan_device_description ggml_vulkan_device_groups ggml_vulkan_device_memory ggml_vulkan_free ggml_vulkan_hard_exit_available ggml_vulkan_init ggml_vulkan_is_backend ggml_vulkan_list_devices ggml_vulkan_p2p_selftest ggml_vulkan_shutdown ggml_vulkan_split_buffer_type ggml_vulkan_split_mul_mat ggml_vulkan_split_row_ranges ggml_vulkan_status

# Vulkan GPU Backend Functions

#' Check if Vulkan support is available
#'
#' Returns TRUE if the package was compiled with Vulkan support.
#' To enable Vulkan, install libvulkan-dev and glslc, then reinstall ggmlR.
#'
#' @return Logical indicating if Vulkan is available
#' @export
#' @examples
#' ggml_vulkan_available()
ggml_vulkan_available <- function() {
  .Call("R_ggml_vulkan_is_available", PACKAGE = "ggmlR")
}

#' Get number of Vulkan devices
#'
#' Returns the number of available Vulkan-capable GPU devices.
#'
#' @return Integer count of Vulkan devices (0 if Vulkan not available)
#' @export
#' @examples
#' \donttest{
#' if (ggml_vulkan_available()) {
#'   ggml_vulkan_device_count()
#' }
#' }
ggml_vulkan_device_count <- function() {
  .Call("R_ggml_vulkan_device_count", PACKAGE = "ggmlR")
}

#' List all Vulkan devices
#'
#' Returns detailed information about all available Vulkan devices.
#'
#' @return List of device information (index, name, memory)
#' @export
#' @examples
#' \donttest{
#' if (ggml_vulkan_available() && ggml_vulkan_device_count() > 0) {
#'   devices <- ggml_vulkan_list_devices()
#'   print(devices)
#' }
#' }
ggml_vulkan_list_devices <- function() {
  .Call("R_ggml_vulkan_list_devices", PACKAGE = "ggmlR")
}

#' Get Vulkan device description
#'
#' Returns a human-readable description of the specified Vulkan device.
#'
#' @param device Device index (0-based)
#' @return Character string with device description
#' @export
#' @examples
#' \donttest{
#' if (ggml_vulkan_available() && ggml_vulkan_device_count() > 0) {
#'   ggml_vulkan_device_description(0)
#' }
#' }
ggml_vulkan_device_description <- function(device = 0L) {
  .Call("R_ggml_vulkan_device_description", as.integer(device), PACKAGE = "ggmlR")
}

#' Get Vulkan device memory
#'
#' Returns free and total memory for the specified Vulkan device.
#'
#' @param device Device index (0-based)
#' @return Named list with 'free' and 'total' memory in bytes
#' @export
#' @examples
#' \donttest{
#' if (ggml_vulkan_available() && ggml_vulkan_device_count() > 0) {
#'   mem <- ggml_vulkan_device_memory(0)
#'   cat("Free:", mem$free / 1e9, "GB\n")
#'   cat("Total:", mem$total / 1e9, "GB\n")
#' }
#' }
ggml_vulkan_device_memory <- function(device = 0L) {
  .Call("R_ggml_vulkan_device_memory", as.integer(device), PACKAGE = "ggmlR")
}

#' Probe Vulkan device groups (NVLink / multi-GPU peer access)
#'
#' Enumerates Vulkan device groups (\code{VK_KHR_device_group}, also known as
#' Linked Device Adapter / LDA) and probes whether the driver reports direct
#' peer memory access between the physical GPUs in each group.
#'
#' A device group with more than one device \emph{and} peer copy/generic memory
#' features is the prerequisite for true GPU-to-GPU transfers routed over NVLink
#' (or PCIe peer-to-peer) through a single device-group logical device — as
#' opposed to sharing memory as an opaque fd between independent devices, which
#' the driver may route through host memory.
#'
#' This is a diagnostic only: it does not create any long-lived device group.
#' On a machine with a single GPU it reports zero multi-device groups. Device
#' groups for compute are effectively an NVIDIA feature; AMD/RADV typically
#' reports only single-device groups.
#'
#' @return A named list with \code{n_groups} (integer, number of device groups
#'   reported by the driver) and \code{report} (character, a human-readable
#'   per-group diagnostic including peer memory features). Use \code{cat()} on
#'   \code{report} to read it.
#' @export
#' @examples
#' \donttest{
#' if (ggml_vulkan_available()) {
#'   g <- ggml_vulkan_device_groups()
#'   cat(g$report)
#' }
#' }
ggml_vulkan_device_groups <- function() {
  .Call("R_ggml_vulkan_device_groups", PACKAGE = "ggmlR")
}

#' Compute tensor-parallel row-split ranges
#'
#' Pure arithmetic helper for the Vulkan tensor-parallel split buffer type: given
#' a number of tensor rows and a per-device weight vector, returns the half-open
#' row range \code{[row_low, row_high)} owned by each device. Row boundaries are
#' rounded down to a fixed granularity, and the last device always covers up to
#' \code{nrows}, so the ranges are contiguous, non-overlapping and cover every
#' row exactly once.
#'
#' This touches no GPU and is exposed mainly to verify the split logic. It is the
#' math behind row-split tensor parallelism (a weight matrix distributed across
#' several GPUs), independent of any actual multi-GPU allocation.
#'
#' @param nrows Number of rows in the tensor (integer).
#' @param n_devices Number of devices to split across (integer, >= 1).
#' @param weights Optional numeric vector of length \code{n_devices} giving the
#'   relative share of rows per device. \code{NULL} (default) splits evenly.
#' @return A named list with \code{row_low} and \code{row_high}: numeric vectors
#'   of length \code{n_devices} holding 0-based, half-open row ranges.
#' @export
#' @examples
#' \donttest{
#' if (ggml_vulkan_available()) {
#'   # 4096 rows across 2 devices, evenly
#'   ggml_vulkan_split_row_ranges(4096L, 2L)
#'   # weighted 3:1
#'   ggml_vulkan_split_row_ranges(4096L, 2L, weights = c(3, 1))
#' }
#' }
ggml_vulkan_split_row_ranges <- function(nrows, n_devices, weights = NULL) {
  w <- if (is.null(weights)) NULL else as.numeric(weights)
  .Call("R_ggml_vk_split_row_ranges", as.numeric(nrows), w,
        as.integer(n_devices), PACKAGE = "ggmlR")
}

#' Opaque-fd device-to-device P2P self-test
#'
#' Exercises the \code{VK_KHR_external_memory_fd} transport used by Vulkan tensor
#' parallelism to move data between GPUs. A byte pattern is written on the source
#' device, its memory is exported as an opaque fd and imported on the destination
#' device, copied into a local buffer there, then read back and compared.
#'
#' When \code{src_device == dst_device} the test runs in \emph{loopback} mode
#' (export and import on the same GPU) — a sanity check of the fd mechanism that
#' touches no inter-device link. When the devices differ it runs
#' \emph{cross-device}: after verifying correctness it times \code{iters}
#' device-to-device copies and reports the achieved bandwidth.
#'
#' Interpreting bandwidth: a measured rate above the PCIe 3.0 x16 ceiling
#' (~16 GB/s) is \emph{empirical} evidence that a faster physical link (e.g.
#' NVLink) carried the bytes. It is \strong{not} a claim that Vulkan used an
#' NVLink API — Vulkan exposes no call to query the route, so the conclusion is
#' inferred from the rate alone, never asserted from the API.
#'
#' @param src_device Source GPU index (0-based).
#' @param dst_device Destination GPU index (0-based). Equal to \code{src_device}
#'   for a loopback sanity check.
#' @param bytes Transfer size in bytes (default 64 MiB).
#' @param iters Number of copies to time for the bandwidth measurement (default 50).
#' @param transport Cross-device transport to exercise: \code{"host-staging"}
#'   (default, portable device->host->device copy — correct on every driver,
#'   PCIe + RAM bounded), \code{"opaque-fd"} (\code{VK_KHR_external_memory_fd} P2P;
#'   works on AMD/RADV but does NOT alias memory cross-device on the NVIDIA
#'   proprietary driver — reads back as zeros), or \code{"device-group"}
#'   (experimental NVIDIA LDA). \code{host-staging} is the transport Stage E3 uses.
#' @return A named list: \code{status} (integer, 0 = data verified, <0 = failure),
#'   \code{gbps} (numeric, measured cross-device bandwidth; 0 for loopback or on
#'   failure) and \code{report} (character diagnostic, incl. the NVLink-vs-PCIe
#'   inference).
#' @export
#' @examples
#' \donttest{
#' if (ggml_vulkan_available()) {
#'   # loopback sanity check on device 0
#'   r <- ggml_vulkan_p2p_selftest(0L, 0L)
#'   cat(r$report)
#'   # cross-device P2P (requires >= 2 GPUs)
#'   if (ggml_vulkan_device_count() >= 2) {
#'     r <- ggml_vulkan_p2p_selftest(0L, 1L)
#'     cat(r$report)
#'   }
#' }
#' }
ggml_vulkan_p2p_selftest <- function(src_device, dst_device,
                                     bytes = 64L * 1024L * 1024L, iters = 50L,
                                     transport = c("host-staging", "opaque-fd", "device-group")) {
  transport <- match.arg(transport)
  t_code <- switch(transport,
                   "host-staging" = 0L,
                   "opaque-fd"    = 1L,
                   "device-group" = 2L)
  .Call("R_ggml_vk_p2p_selftest",
        as.integer(src_device), as.integer(dst_device),
        as.numeric(bytes), as.integer(iters), t_code, PACKAGE = "ggmlR")
}

#' Tensor-parallel matrix multiply across Vulkan devices
#'
#' Computes \code{Y = X \%*\% t(W)} with the rows of the weight matrix \code{W}
#' split across \code{n_devices} GPUs (true tensor parallelism), the activations
#' \code{X} broadcast to every device, and the resulting column slices gathered
#' back into a single matrix. This is Stage E3 of the Vulkan tensor-parallelism
#' work: it exercises the row-split + per-device compute + cross-device gather
#' path end to end on real hardware.
#'
#' Each device owns a contiguous band of \code{W}'s rows (via the same row-split
#' math as \code{\link{ggml_vulkan_split_row_ranges}}) and produces the matching
#' band of \code{Y}'s columns; the bands are gathered through the transport (host
#' staging by default). This is a validation / correctness entry point, not a hot
#' inference path — it stands up one backend per device per call and round-trips
#' through host memory.
#'
#' @param W Weight matrix, \code{N x K} (N output features, K input features).
#'   Its rows are split across the devices.
#' @param X Activation matrix, \code{M x K} (M samples, K input features),
#'   broadcast to every device.
#' @param n_devices Number of GPUs to split across (default: all available).
#'   Ignored if \code{device_ids} is given (its length is used instead).
#' @param weights Optional numeric vector of length \code{n_devices} giving the
#'   relative share of \code{W}'s rows per device (e.g. \code{c(3, 1)} for a 3:1
#'   split). \code{NULL} (default) splits the rows evenly.
#' @param device_ids Optional integer vector of physical GPU indices (0-based) to
#'   split across, e.g. \code{c(2, 3)} for the second replica of a TPxDP layout.
#'   \code{NULL} (default) uses devices \code{0..n_devices-1}. When supplied,
#'   \code{n_devices} is taken from its length.
#' @param transport Cross-device gather transport: \code{"host-staging"} (default,
#'   portable and correct on every driver), \code{"opaque-fd"} or
#'   \code{"device-group"} (see \code{\link{ggml_vulkan_p2p_selftest}}).
#' @return The \code{M x N} result matrix, equal to \code{X \%*\% t(W)} up to the
#'   GPU's floating-point accumulation. Errors if the split compute fails; the
#'   C-level diagnostic is attached as attribute \code{"report"}.
#' @export
#' @examples
#' \donttest{
#' if (ggml_vulkan_available() && ggml_vulkan_device_count() >= 2) {
#'   W <- matrix(rnorm(8 * 4), nrow = 8)   # 8 output features x 4 inputs
#'   X <- matrix(rnorm(3 * 4), nrow = 3)   # 3 samples x 4 inputs
#'   Y <- ggml_vulkan_split_mul_mat(W, X, n_devices = 2)
#'   max(abs(Y - X %*% t(W)))              # ~1e-3 (GPU f16 accumulation)
#' }
#' }
ggml_vulkan_split_mul_mat <- function(W, X, n_devices = ggml_vulkan_device_count(),
                                      weights = NULL, device_ids = NULL,
                                      transport = c("host-staging", "opaque-fd", "device-group")) {
  transport <- match.arg(transport)
  t_code <- switch(transport,
                   "host-staging" = 0L,
                   "opaque-fd"    = 1L,
                   "device-group" = 2L)
  if (!is.null(device_ids)) {
    device_ids <- as.integer(device_ids)
    n_devices  <- length(device_ids)
  }
  W <- as.matrix(W)
  X <- as.matrix(X)
  N <- nrow(W); K <- ncol(W)
  M <- nrow(X)
  if (ncol(X) != K) {
    stop(sprintf("X has %d columns but W has %d (both are the K/input dimension)",
                 ncol(X), K))
  }
  w <- if (is.null(weights)) NULL else as.numeric(weights)

  # Pack into the column-major flat layout the C API expects:
  #   w[n*K + k] = W[n, k]  ->  as.numeric(t(W))  (row n contiguous)
  #   x[m*K + k] = X[m, k]  ->  as.numeric(t(X))
  res <- .Call("R_ggml_vk_split_mul_mat",
               as.numeric(t(W)), as.numeric(t(X)),
               as.numeric(N), as.numeric(K), as.numeric(M),
               w, as.integer(n_devices), device_ids, t_code, PACKAGE = "ggmlR")

  if (res$status != 0L || is.null(res$y)) {
    stop("ggml_vulkan_split_mul_mat failed:\n", res$report)
  }
  # y[m*N + n] = Y[m, n]; a column-major fill of an N x M matrix, transposed -> M x N.
  Y <- t(matrix(res$y, nrow = N, ncol = M))
  attr(Y, "report") <- res$report
  Y
}

#' Explicitly tear down the Vulkan instance and devices
#'
#' Waits for all Vulkan devices to go idle, then destroys the devices and the
#' Vulkan instance. Call this at the end of a script that used multiple GPUs
#' (tensor / pipeline parallelism), \emph{before} the process exits.
#'
#' Why this exists: with several Vulkan devices active, R's own teardown at
#' process exit can run \emph{after} the Vulkan loader / Mesa ICD libraries have
#' been unmapped, so the device destructors call into unmapped memory and the
#' process crashes with a segfault \emph{after} your results were already printed.
#' Calling \code{ggml_vulkan_shutdown()} while the process is still alive runs the
#' teardown while the loader is still mapped, avoiding that crash. It is only
#' needed for exit cleanliness — your computed results are unaffected either way.
#'
#' Idempotent and a no-op if Vulkan was never initialized. After this call the
#' next Vulkan operation transparently re-initializes the instance from scratch,
#' so it is safe to call mid-session too.
#'
#' The plain teardown alone does not always win the exit-time race: with backends
#' created inside pipeline / tensor-parallel forwards, the loader-static-destruction
#' segfault (address ending \code{f1ba0}) can still fire flakily \emph{after} your
#' results are printed, because no R exit hook runs before R unmaps the loader.
#' For a guaranteed-clean exit, pass \code{hard = TRUE} as the \strong{last}
#' statement of a standalone script: after teardown it calls \code{_exit(0)},
#' terminating the process immediately without running R's / the C runtime's exit
#' handlers or unmapping any shared library — so there is no loader teardown phase
#' left to crash. Because it bypasses R's normal shutdown, only use
#' \code{hard = TRUE} at the very end of a script, never mid-session or in a
#' package/interactive context.
#'
#' @section Hard exit is opt-in at build time:
#' The \code{_exit()} path is \strong{compiled out by default}, because CRAN
#' Repository Policy forbids a package from terminating the user's R session. In
#' such a build \code{hard = TRUE} performs the normal teardown and emits a
#' \code{\link{warning}} — it is never silently ignored — so the exit-time race
#' described above can still fire. To compile it in, build from source with
#' \code{R CMD INSTALL . --configure-args="--enable-hard-exit"} (on Windows set
#' \code{Sys.setenv(GGML_VK_HARD_EXIT = "1")} first, since R ignores
#' \code{configure.args} there). Check the current build with
#' \code{\link{ggml_vulkan_hard_exit_available}()}.
#'
#' @param hard Logical. If \code{TRUE}, call \code{_exit(status)} after teardown to
#'   skip all exit handlers and guarantee no exit-time segfault; never returns.
#'   Requires a build with \code{--enable-hard-exit} (see above); otherwise it
#'   warns and returns normally. Default \code{FALSE} (safe to call mid-session).
#' @param status Integer process exit code used only when \code{hard = TRUE}
#'   (passed to \code{_exit()}). Defaults to \code{0L} (success). If you call this
#'   from an error path (e.g. a \code{tryCatch} handler after a stage failed),
#'   pass a non-zero \code{status} so the failed run does not exit \code{0} and
#'   mask the failure. Ignored when \code{hard = FALSE}.
#' @return Invisibly \code{NULL}. Does not return when \code{hard = TRUE} \emph{and}
#'   the hard-exit path is compiled in (see the section above).
#' @seealso \code{\link{ggml_vulkan_hard_exit_available}}
#' @export
#' @examples
#' \donttest{
#' if (ggml_vulkan_available() && ggml_vulkan_device_count() >= 2) {
#'   W <- matrix(rnorm(2048 * 64), nrow = 2048)
#'   X <- matrix(rnorm(4 * 64), nrow = 4)
#'   Y <- ggml_vulkan_split_mul_mat(W, X, n_devices = 2)
#'   ggml_vulkan_shutdown()   # clean up before the script exits
#' }
#' }
ggml_vulkan_shutdown <- function(hard = FALSE, status = 0L) {
  if (isTRUE(hard) && !ggml_vulkan_hard_exit_available()) {
    warning("ggml_vulkan_shutdown(hard = TRUE): hard exit is not compiled into ",
            "this build, falling back to the normal teardown. The exit-time ",
            "Vulkan loader race may still segfault after your results are ",
            "printed. To enable it, rebuild with ",
            "R CMD INSTALL . --configure-args=\"--enable-hard-exit\" ",
            "(Windows: Sys.setenv(GGML_VK_HARD_EXIT = \"1\") before installing).",
            call. = FALSE)
  }
  invisible(.Call("R_ggml_vk_shutdown", isTRUE(hard), as.integer(status),
                  PACKAGE = "ggmlR"))
}

#' Is the Vulkan hard-exit path compiled in?
#'
#' Reports whether this build of ggmlR was compiled with
#' \code{-DGGML_VK_HARD_EXIT}, which is what makes
#' \code{\link{ggml_vulkan_shutdown}(hard = TRUE)} actually call \code{_exit()}.
#'
#' The hard-exit path is \strong{disabled by default}: CRAN Repository Policy
#' forbids a package from terminating the user's R session, so the released
#' package must not link \code{_exit()}. Builds from source can opt in with
#' \code{R CMD INSTALL . --configure-args="--enable-hard-exit"} (on Windows, set
#' \code{Sys.setenv(GGML_VK_HARD_EXIT = "1")} before installing, because R there
#' ignores \code{configure.args}).
#'
#' When it is not compiled in, \code{ggml_vulkan_shutdown(hard = TRUE)} performs
#' the normal teardown and emits a \code{\link{warning}} rather than silently
#' ignoring the request.
#'
#' @return \code{TRUE} if the hard-exit path is available, otherwise \code{FALSE}.
#' @seealso \code{\link{ggml_vulkan_shutdown}}
#' @export
#' @examples
#' ggml_vulkan_hard_exit_available()
ggml_vulkan_hard_exit_available <- function() {
  .Call("R_ggml_vk_hard_exit_available", PACKAGE = "ggmlR")
}

#' Create a Vulkan tensor-split buffer type
#'
#' Builds (or fetches from a cache) a \code{ggml} buffer type that row-splits a
#' weight tensor across \code{n_devices} GPUs for tensor parallelism. This is the
#' factory the graph allocator talks to (Stage E4): a weight placed in this buffer
#' type has its rows distributed across the devices, each holding a contiguous
#' band (see \code{\link{ggml_vulkan_split_row_ranges}}). Non-split tensors fall
#' back to \code{main_device}.
#'
#' The buffer type is cached in C and lives for the rest of the session; the
#' returned external pointer has no finalizer and must not be freed.
#'
#' @param n_devices Number of GPUs to split across (default: all available).
#'   Ignored if \code{device_ids} is given (its length is used instead).
#' @param main_device Device index (0-based) holding non-split fallbacks (default 0).
#' @param weights Optional numeric vector of length \code{n_devices} giving the
#'   relative row share per device (e.g. \code{c(3, 1)}). \code{NULL} (default)
#'   splits evenly.
#' @param device_ids Optional integer vector of physical GPU indices (0-based) the
#'   split slots map onto, e.g. \code{c(2, 3)}. \code{NULL} (default) uses
#'   \code{0..n_devices-1}. When supplied, \code{n_devices} is its length.
#' @param transport Cross-device gather transport for the eventual compute:
#'   \code{"host-staging"} (default), \code{"opaque-fd"} or \code{"device-group"}.
#' @param probe A numeric vector \code{c(N, K)} describing a probe weight tensor
#'   (\code{N} rows, \code{K} columns) whose split allocation size to report, or
#'   \code{NULL} (default) to skip. Useful for inspecting how much total VRAM a
#'   given weight would occupy across the split.
#' @return A named list: \code{ptr} (external pointer to the cached buffer type),
#'   \code{name} (its config-derived name) and \code{alloc_size} (sum of the
#'   padded per-device slice sizes for \code{probe}, in bytes; 0 if no probe).
#' @export
#' @examples
#' \donttest{
#' if (ggml_vulkan_available() && ggml_vulkan_device_count() >= 2) {
#'   bt <- ggml_vulkan_split_buffer_type(n_devices = 2, probe = c(2048, 64))
#'   bt$name
#'   bt$alloc_size   # total bytes for a 2048x64 f32 weight across 2 devices
#' }
#' }
ggml_vulkan_split_buffer_type <- function(n_devices = ggml_vulkan_device_count(),
                                          main_device = 0L, weights = NULL,
                                          device_ids = NULL,
                                          transport = c("host-staging", "opaque-fd", "device-group"),
                                          probe = NULL) {
  transport <- match.arg(transport)
  t_code <- switch(transport,
                   "host-staging" = 0L,
                   "opaque-fd"    = 1L,
                   "device-group" = 2L)
  if (!is.null(device_ids)) {
    device_ids <- as.integer(device_ids)
    n_devices  <- length(device_ids)
  }
  w <- if (is.null(weights)) NULL else as.numeric(weights)
  probe_N <- if (is.null(probe)) 0 else as.numeric(probe[1])
  probe_K <- if (is.null(probe)) 0 else as.numeric(probe[2])
  .Call("R_ggml_vk_split_buffer_type",
        as.integer(main_device), w, as.integer(n_devices), device_ids, t_code,
        probe_N, probe_K, PACKAGE = "ggmlR")
}

#' Initialize Vulkan backend
#'
#' Creates a Vulkan backend for the specified device.
#' The backend must be freed with ggml_vulkan_free() when done.
#'
#' @param device Device index (0-based, default 0)
#' @return Vulkan backend pointer
#' @export
#' @examples
#' \donttest{
#' if (ggml_vulkan_available() && ggml_vulkan_device_count() > 0) {
#'   backend <- ggml_vulkan_init(0)
#'   print(ggml_vulkan_backend_name(backend))
#'   ggml_vulkan_free(backend)
#' }
#' }
ggml_vulkan_init <- function(device = 0L) {
  .Call("R_ggml_vulkan_init", as.integer(device), PACKAGE = "ggmlR")
}

#' Free Vulkan backend
#'
#' Releases resources associated with the Vulkan backend.
#'
#' @param backend Vulkan backend pointer from ggml_vulkan_init()
#' @return NULL (invisible)
#' @export
#' @examples
#' \donttest{
#' if (ggml_vulkan_available() && ggml_vulkan_device_count() > 0) {
#'   backend <- ggml_vulkan_init(0)
#'   ggml_vulkan_free(backend)
#' }
#' }
ggml_vulkan_free <- function(backend) {
  invisible(.Call("R_ggml_vulkan_free", backend, PACKAGE = "ggmlR"))
}

#' Check if backend is Vulkan
#'
#' Returns TRUE if the given backend is a Vulkan backend.
#'
#' @param backend Backend pointer
#' @return Logical indicating if backend is Vulkan
#' @export
#' @examples
#' \donttest{
#' if (ggml_vulkan_available() && ggml_vulkan_device_count() > 0) {
#'   vk_backend <- ggml_vulkan_init(0)
#'   cpu_backend <- ggml_backend_cpu_init()
#'
#'   ggml_vulkan_is_backend(vk_backend)  # TRUE
#'   ggml_vulkan_is_backend(cpu_backend) # FALSE
#'
#'   ggml_vulkan_free(vk_backend)
#'   ggml_backend_free(cpu_backend)
#' }
#' }
ggml_vulkan_is_backend <- function(backend) {
  .Call("R_ggml_vulkan_is_backend", backend, PACKAGE = "ggmlR")
}

#' Get Vulkan backend name
#'
#' Returns the name of the Vulkan backend (includes device info).
#'
#' @param backend Vulkan backend pointer
#' @return Character string with backend name
#' @export
#' @examples
#' \donttest{
#' if (ggml_vulkan_available() && ggml_vulkan_device_count() > 0) {
#'   backend <- ggml_vulkan_init(0)
#'   print(ggml_vulkan_backend_name(backend))
#'   ggml_vulkan_free(backend)
#' }
#' }
ggml_vulkan_backend_name <- function(backend) {
  .Call("R_ggml_vulkan_backend_name", backend, PACKAGE = "ggmlR")
}

#' Get Vulkan device capabilities
#'
#' Returns hardware capabilities for the specified Vulkan device.
#'
#' @param device Device index (0-based, default 0)
#' @return Named list: coopmat_support, coopmat1_fa_support, fp16, subgroup_size, subgroup_no_shmem
#' @export
ggml_vulkan_device_caps <- function(device = 0L) {
  .Call("R_ggml_vulkan_device_caps", as.integer(device), PACKAGE = "ggmlR")
}

#' Print Vulkan status
#'
#' Prints information about Vulkan availability and devices.
#'
#' @return NULL (invisible), prints status to console
#' @export
#' @examples
#' ggml_vulkan_status()
ggml_vulkan_status <- function() {
  available <- ggml_vulkan_available()

  if (!available) {
    cat("Vulkan: NOT AVAILABLE\n")
    cat("  To enable: install libvulkan-dev and glslc, then reinstall ggmlR\n")
    cat("  Ubuntu/Debian: sudo apt install libvulkan-dev glslc\n")
    return(invisible(NULL))
  }

  count <- ggml_vulkan_device_count()
  cat("Vulkan: AVAILABLE\n")
  cat("  Devices:", count, "\n")

  if (count > 0) {
    devices <- ggml_vulkan_list_devices()
    for (i in seq_along(devices)) {
      dev <- devices[[i]]
      cat(sprintf("  [%d] %s\n", dev$index, dev$name))
      cat(sprintf("      Memory: %.2f GB free / %.2f GB total\n",
                  dev$free_memory / 1e9, dev$total_memory / 1e9))
    }
  }

  invisible(NULL)
}

Try the ggmlR package in your browser

Any scripts or data that you put into this service are public.

ggmlR documentation built on July 14, 2026, 1:08 a.m.