--- title: "Keep an R workflow on the GPU" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Keep an R workflow on the GPU} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>", eval = FALSE) ``` A GPU is most useful when data are not repeatedly copied between R and the GPU. This guide shows the simple pattern: upload once, run several CUDA tasks, and bring back only the result you need. ## Require CUDA at the start Use a strict selection before a long analysis: ```{r select} library(cudaverse) cuda_select_device("cuda") ``` If CUDA is not ready, this call stops and explains what is missing. It never turns a requested GPU workflow into an unnoticed non-GPU run. ## Upload once and reuse the tensor Construct a `cudatensor` once, then apply arithmetic, reshape, transpose, subsetting, reduction, and matrix multiplication without reconstructing the input for every call. ```{r tensor-pipeline} set.seed(1) x <- matrix(rnorm(10000 * 100), nrow = 10000) x_gpu <- cuda_tensor(x, device = "cuda", dtype = "float32") means_gpu <- tensor_mean(x_gpu, dim = 1) centered_gpu <- x_gpu - means_gpu selected_gpu <- centered_gpu[, 1:50, drop = FALSE] gram_gpu <- tensor_matmul(t(selected_gpu), selected_gpu) tensor_device(gram_gpu) cuda_provenance(gram_gpu) ``` Supported reshape and selection operations stay on the GPU, so they can feed the next calculation without downloading the full tensor. Only cross the host boundary when an ordinary R object is required: ```{r tensor-download} gram <- to_cpu(gram_gpu) ``` Calling `to_cpu()` inside each stage would add transfers and erase much of the reason to use the GPU. ## Keep PCA followed by kNN on CUDA The native PCA object retains device storage for its scores, so exact kNN can consume `pca$x` directly. ```{r pca-knn} pca <- cuda_pca( x_gpu, n_components = 20, device = "cuda" ) neighbors <- cuda_knn( pca$x, k = 15, batch_size = 256, device = "cuda" ) cuda_provenance(pca) cuda_provenance(neighbors) head(neighbors$index) ``` The public kNN result contains ordinary R index and distance matrices because those are normally the final analysis output. The expensive PCA, distance blocks, and stable top-k stages remain CUDA stages. ## Keep sparse preprocessing on CUDA For sparse data, upload a `Matrix` object once and continue with normalization, PCA, and exact kNN: ```{r sparse-resident} counts <- Matrix::rsparsematrix(50000, 128, density = 0.01) counts@x <- abs(counts@x) counts_gpu <- cuda_sparse(counts, device = "cuda") normalized_gpu <- sparse_normalize( counts_gpu, margin = "rows", scale_factor = 10000, log1p = TRUE ) sparse_pca <- cuda_pca(normalized_gpu, n_components = 20, device = "cuda") sparse_neighbors <- cuda_knn( sparse_pca$x, k = 15, device = "cuda" ) sparse_info(normalized_gpu) cuda_provenance(sparse_neighbors) ``` Use `to_dgCMatrix()` only when a downstream R package specifically needs a host `dgCMatrix`. ## Read provenance `cuda_provenance()` shows where each part of a calculation ran. The most useful fields are: | Field | Question answered | |---|---| | `operation` | What did this stage do? | | `device` | Where did the computation run? | | `backend` | Which cudaverse implementation performed it? | | `output_device` | Where was the result left for the next step? | Multi-stage algorithms retain a stage list. Inspect it directly when checking residency: ```{r inspect-stages} prov <- cuda_provenance(neighbors) prov$stages ``` A strict `device = "cuda"` request cannot silently fall back. A hybrid high-level workflow can still contain a deliberately documented host stage; that stage has its own record rather than being described as GPU-resident. ## Control memory and transfer costs ```{r memory} cuda_memory_info("cuda") ``` - Prefer `float32` when its numerical precision is sufficient; it halves the value storage compared with `float64`. - Reuse `cudatensor` and `cudasparse` objects across operations. - Use `cuda_knn()` instead of `cuda_distance()` when a full dense distance matrix is not needed. - Lower `batch_size` when exact distance or kNN blocks approach the available device memory. - Print or download large CUDA objects only when the host values are needed. ## Confirm the backend used The lightweight path does not require R `torch`. A typical native result should show CUDA and native in its stage record: ```{r assert-native} stages <- cuda_provenance(neighbors)$stages vapply(stages, `[[`, character(1), "device") vapply(stages, `[[`, character(1), "backend") ``` Some records describe preparation or returning the final R object rather than a numerical calculation. The [operation coverage guide](backend-support.html) explains what to expect for each task.