| name | rapids-first |
| description | Use only when the user explicitly mentions "rapids-first" or "RAPIDS first". Prefer the RAPIDS GPU stack and zero-code accelerators when writing Python data-science, ML, ETL, graph, image-processing, or vector-search code. |
RAPIDS-first
When to Enable
Enable this skill if and only if the user's request explicitly contains rapids-first (or the synonymous wording "RAPIDS first"). Do not apply it proactively in other scenarios, to avoid modifying code outside the user's expectations.
Applicable Scenarios
Once rapids-first is enabled, all "writing Python code" subtasks are handled according to the RAPIDS-first principle, not limited to rewriting existing CPU code:
- Rewriting existing CPU code: replace numpy / pandas / sklearn / scipy / networkx / skimage / faiss / umap / hdbscan / dask.dataframe calls with the corresponding RAPIDS APIs.
- Adding new features / implementing new algorithms: when writing implementations from scratch, prefer RAPIDS APIs as the foundation rather than numpy / scipy / sklearn. Example: for matrix decomposition → directly choose
cuml.decomposition over sklearn.decomposition; for nearest neighbors → directly choose cuvs.neighbors / cuml.neighbors over faiss / sklearn.neighbors; for inference on an already-trained tree model (XGBoost / LightGBM / RandomForest) → directly choose nvforest over the framework's native predict.
- Creating new scripts (ETL / training / inference / batch processing): use
cudf.read_* / dask_cudf.read_* for data loading, cupy for matrix computation, cudf + cuml.preprocessing for feature engineering, cuml / cugraph for model training, cucim.skimage / cupyx.scipy.ndimage for image processing.
- Adding new tests: use RAPIDS as much as possible for test data generation and assertion comparison (
cudf.testing / cuml.testing, etc.); let numpy / pandas appear only for RAPIDS↔CPU numerical comparisons, with a one-shot conversion at the boundary.
- Fixing bugs: even when just fixing a single numpy function call, if a suitable RAPIDS equivalent exists in the context, replace it in passing.
In short: after enabling this skill, "think first how to do it with RAPIDS" takes priority over "think first how to do it with numpy / pandas / sklearn". Only fall back to the CPU path when the RAPIDS stack truly has no corresponding implementation.
Three Core Principles
- Zero-code acceleration first: RAPIDS provides monkey-patch or backend-dispatch mechanisms for the most common CPU libraries (pandas, sklearn, umap, hdbscan, networkx, dask.dataframe), allowing existing CPU code to run on GPU without changing any imports. Use zero-code whenever possible — especially in the "rewriting existing CPU code" scenario.
- Avoid redundant CPU↔GPU conversions: once data is on GPU, keep it on GPU as much as possible, minimizing calls like
.to_pandas() / .to_numpy() / .get() that trigger device synchronization and copying; only do a one-shot downlink at the pipeline endpoint or at necessary output/visualization boundaries. Mixing cupy.ndarray with numpy.ndarray (or cudf.DataFrame with pandas.DataFrame) triggers implicit synchronization and should be proactively eliminated.
- Precise API targeting: this skill directory ships a complete API index
apis/<pkg>.txt for each RAPIDS package, with each line formatted as "full path(signature) - first line of docstring". A single ripgrep search retrieves both the call syntax and purpose, avoiding writing signatures from memory (cuML and sklearn often differ in parameter order, default values, output_type, etc.).
Directory Structure
${SKILL_DIR}/
├── SKILL.md # This file
├── cpu-rapids-cuda-mapping.tsv # Index of CPU library → RAPIDS package (TSV)
├── fetch_rapids_apis.py # Script that (re-)generates apis/<pkg>.txt
└── apis/ # One API index file per package
├── cupy.txt # CuPy (replaces numpy)
├── cupyx.txt # CuPy.scipy and other extensions (replaces scipy)
├── cudf.txt # cuDF (replaces pandas)
├── dask_cudf.txt # dask-cuDF
├── cuml.txt # cuML (replaces scikit-learn / umap / hdbscan / statsmodels.tsa)
├── cugraph.txt # cuGraph (explicit graph algorithm APIs)
├── nx_cugraph.txt # nx-cugraph (NetworkX backend)
├── cuxfilter.txt # cuXfilter
├── cucim.txt # cuCIM (replaces scikit-image / openslide)
├── pylibraft.txt # RAFT primitives
├── raft_dask.txt # RAFT-Dask communication layer
├── cuvs.txt # cuVS (replaces faiss and other vector search)
└── nvforest.txt # nvForest (GPU/CPU forest inference; successor to cuML FIL)
${SKILL_DIR} is the directory containing this SKILL.md — typically ~/.claude/skills/rapids-first (user-level) or .claude/skills/rapids-first (project-level), with .claude replaced by .agents for Codex. Export it once before the first rg:
SKILL_DIR=$HOME/.claude/skills/rapids-first
Standard Workflow
Step 1: Identify the CPU library to replace/select
- Rewrite scenario: scan the user's code to find the imports to accelerate (
import numpy as np, import pandas as pd, from sklearn.cluster import KMeans, import scipy.ndimage, import networkx as nx, from skimage import ..., import faiss / hnswlib / umap / hdbscan, import dask.dataframe as dd, etc.).
- New code scenario: against the functional requirements, first mentally write out "if using a CPU library, which one would I use" (pandas? sklearn? scipy?), then follow the steps below to convert it to RAPIDS.
Step 2: Use ripgrep on mapping.tsv to locate the RAPIDS package
cpu-rapids-cuda-mapping.tsv fields:
cpu_module rapids_target zero_code notes
For an exact top-level package lookup use ^pkg\t; for fuzzy submodule lookup use plain grep:
rg -P '^pandas\t' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"
rg -P '^sklearn\.cluster\t' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"
rg -P '^scipy\.ndimage\t' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"
rg -P '^networkx\t' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"
rg -i 'faiss' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"
Read out rapids_target (target import path) and zero_code (zero-code solution identifier).
Step 3: Check whether the zero-code solution applies
If the zero_code field is not - and the scenario allows it (primarily "rewriting existing CPU code"), prefer the zero-code solution. Canonical activation methods:
| zero_code identifier | Activation method |
|---|
cudf.pandas | CLI: python -m cudf.pandas script.py Jupyter first cell: %load_ext cudf.pandas In-program (must precede import pandas): import cudf.pandas; cudf.pandas.install() |
cuml.accel | CLI: python -m cuml.accel script.py Jupyter first cell: %load_ext cuml.accel Environment variable: CUML_ACCEL_ENABLED=1 In-program (must precede import sklearn / umap / hdbscan): from cuml.accel import install; install() |
nx-cugraph-backend | Environment variable: NX_CUGRAPH_AUTOCONFIG=True (automatic) Or single call: nx.pagerank(G, backend="cugraph") Or config: nx.config.backend_priority = ["cugraph"] (NetworkX ≥ 3.2) |
dask-cudf-backend | dask.config.set({"dataframe.backend": "cudf"}), continue using dask.dataframe; often combined with dask_cuda.LocalCUDACluster |
Once a zero-code solution is enabled, the original code does not need any import changes; unsupported APIs automatically fall back to CPU without raising errors.
Applicability decision:
- Rewrite scenario + user wants "to make existing code run faster" → zero-code
- New code / user wants explicit GPU APIs / user wants the code to clearly read as using RAPIDS → proceed to the explicit import in Step 4
Step 4: Explicit import replacement (when zero-code does not apply, or when writing new code)
Two-step API location:
-
Get rapids_target from mapping.tsv. Examples: numpy → cupy, scipy.ndimage → cupyx.scipy.ndimage, sklearn.cluster → cuml.cluster, faiss → cuvs.neighbors.
-
Search for the target API in ${SKILL_DIR}/apis/<pkg>.txt using ripgrep:
rg -i '\.KMeans\(' "$SKILL_DIR/apis/cuml.txt"
rg -i '^cuml\.cluster\.KMeans' "$SKILL_DIR/apis/cuml.txt"
rg -i '\.gaussian_filter\(' "$SKILL_DIR/apis/cupyx.txt"
rg -i '\.read_parquet\(' "$SKILL_DIR/apis/cudf.txt"
rg -i '\.fft2\(' "$SKILL_DIR/apis/cupy.txt"
rg '^cuml\.cluster\.' "$SKILL_DIR/apis/cuml.txt"
rg '^cuvs\.neighbors\.cagra\.' "$SKILL_DIR/apis/cuvs.txt"
rg -i '\.PCA\(' "$SKILL_DIR/apis/cuml.txt" "$SKILL_DIR/apis/cuvs.txt"
Each matched line is formatted as "full path(signature) - first line of docstring"; copy the signature directly when writing code.
Step 5: Respect the GPU data lifecycle
- Continuous GPU pipeline: avoid mid-stream round-trips like
cudf.to_pandas() → processing → cudf.from_pandas(). If some intermediate step does not yet provide a GPU version, search ${SKILL_DIR}/apis/*.txt once more to confirm before falling back.
- No device mixing: mixing
cupy.ndarray with numpy.ndarray, or cudf.DataFrame with pandas.DataFrame, triggers implicit copying and forces stream synchronization. All array types within the same computation graph should be consistent.
- I/O lands directly on GPU: use
cudf.read_csv / cudf.read_parquet / cudf.read_orc / cudf.read_json, etc., to go from disk straight to GPU memory, rather than pandas.read_* followed by an upload; the same applies to cuml.preprocessing, which works directly on cuDF.
- Default dtypes: some cuDF / cuML operators default to
float32, while sklearn defaults to float64. For high-precision comparisons, explicitly pass dtype=cupy.float64 or convert_dtype=False.
- Fail-fast, no existence checks: importing a RAPIDS package already requires CUDA to be available; do not write defensive fallbacks like
try: import cupy except: import numpy as cupy — if the environment is missing, let it fail directly.
Search Examples
Example 1: Accelerate from sklearn.cluster import KMeans to GPU (rewrite scenario)
$ rg -P '^sklearn\.cluster\t' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"
sklearn.cluster cuml.cluster cuml.accel KMeans, DBSCAN, HDBSCAN, AgglomerativeClustering
- Zero-code preferred: have the user run with
python -m cuml.accel script.py; the original code stays untouched.
- Explicit replacement (if the user prefers):
$ rg -i '^cuml\.cluster\.KMeans\(' "$SKILL_DIR/apis/cuml.txt"
cuml.cluster.KMeans(*, handle=None, n_clusters=8, max_iter=300, tol=0.0001, ...) - KMeans is a basic but powerful clustering method ...
Per the signature, change directly to from cuml.cluster import KMeans.
Example 2: Implement a matrix FFT pipeline from scratch (new code scenario)
Requirement: perform fft2 + magnitude + normalization on a batch of 2D matrices.
- "If using CPU, I would use numpy.fft + numpy" → look up the mapping:
$ rg -P '^numpy(\.fft)?\t' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"
numpy cupy - import cupy as cp; substitute cp for np
numpy.fft cupy.fft - -
- Look up the specific API:
$ rg -i '\.fft2\(' "$SKILL_DIR/apis/cupy.txt"
cupy.fft.fft2(a, s=None, axes=(-2, -1), norm=None) - Compute the two-dimensional FFT.
- Write the code: a one-shot upload via
cp.asarray → cp.fft.fft2 → cp.abs → cp.linalg.norm, all done on GPU, then .get() or write the file output at the very end.
Example 3: Write unit tests for a new algorithm implementation (new test scenario)
Requirement: test that a GPU-implemented PCA produces numerically consistent results with sklearn's PCA.
- Data generation:
cuml.datasets.make_blobs(...) or cupy.random.RandomState(seed).normal(...) — not sklearn.datasets / numpy.random.
- Run on GPU:
cuml.decomposition.PCA(...).fit_transform(X).
- CPU comparison: one-shot downlink with
X.get() → sklearn.decomposition.PCA(...).fit_transform(X_cpu).
- Assertion:
cupy.testing.assert_allclose(...), or numpy.testing.assert_allclose on the CPU side (because the comparison target is numpy).
- Across the whole process, data transfer happens only once, at step 3.
Example 4: Replace faiss for ANN search (rewrite scenario)
$ rg -i 'faiss' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"
faiss cuvs.neighbors - ANN vector search; CAGRA/IVF-Flat/IVF-PQ/HNSW/Brute-Force ...
$ rg '^cuvs\.neighbors\.cagra\.' "$SKILL_DIR/apis/cuvs.txt" | head
cuvs.neighbors.cagra.IndexParams(...) - ...
cuvs.neighbors.cagra.SearchParams(...) - ...
cuvs.neighbors.cagra.build(index_params, dataset, ...) - ...
cuvs.neighbors.cagra.search(search_params, index, queries, k, ...) - ...
Rewrite per the signatures: build builds the index, search queries k-nearest neighbors. CAGRA is cuVS's flagship GPU graph index.
Example 5: Accelerate XGBoost / LightGBM / RandomForest inference (rewrite scenario)
nvForest runs inference for already-trained tree models on GPU (and CPU); it does not train. It is the standalone successor to cuML's Forest Inference Library — cuml.fil / cuml.ForestInference is deprecated in RAPIDS 26.06 in favor of nvforest, so route forest prediction here while keeping training in XGBoost / LightGBM / cuml.ensemble.
$ rg -P '^(xgboost|lightgbm)\t' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"
xgboost nvforest - forest INFERENCE only (no training): load_model(path, model_type='xgboost_ubj'/...) -> .predict()/.predict_proba(); ...
lightgbm nvforest - forest INFERENCE only: load_model(path, model_type='lightgbm') -> .predict(); ...
$ rg '^nvforest\.load' "$SKILL_DIR/apis/nvforest.txt"
nvforest.load_from_sklearn(skl_model, *, device='auto', ...) -> nvforest._base.ForestInference - Load a Scikit-Learn forest model to nvForest
nvforest.load_from_treelite_model(tl_model, *, device='auto', ...) -> nvforest._base.ForestInference - Load a Treelite forest model to nvForest
nvforest.load_model(model_file, *, model_type=None, device='auto', ...) -> nvforest._base.ForestInference - Load a model into nvForest from a serialized model file.
Pick the loader that matches where the model comes from — load_model for a serialized XGBoost / LightGBM file (set model_type, e.g. 'xgboost_ubj' / 'xgboost_json' / 'lightgbm'), load_from_sklearn for an in-memory scikit-learn / cuML forest, load_from_treelite_model for a Treelite handle — then call .predict() / .predict_proba() / .apply() on the returned object. Pass device='gpu' to force GPU (default 'auto' falls back to CPU when no GPU is present); X may be a numpy.ndarray or cupy.ndarray.
When RAPIDS Has No Corresponding Implementation
Handle by priority:
- Search for similar keywords again in
${SKILL_DIR}/apis/<pkg>.txt. RAPIDS naming often differs slightly from sklearn / scipy (e.g., sklearn's cross_val_score has no direct counterpart in cuml, but can be reproduced with train_test_split plus a handwritten loop; sklearn's Pipeline is also absent in cuml but can be chained manually).
- Check neighboring packages: when a scikit-image operator is not found in cuCIM, see whether
cupyx.scipy.ndimage / cupy can implement it by hand.
- When there is still no counterpart, explicitly tell the user that the operator is not in the current RAPIDS stack, then use the CPU version and consolidate data transfers at the CPU↔GPU boundary to minimize the number of transfers (ideal: the entire pipeline does
.get() / .to_pandas() only once at the end).
(Re-)Generating apis/
- After any RAPIDS package upgrade, run
python "$SKILL_DIR/fetch_rapids_apis.py" to regenerate ${SKILL_DIR}/apis/<pkg>.txt.
- You can also target specific packages only:
python "$SKILL_DIR/fetch_rapids_apis.py" --packages cudf cuml.
- The script writes back to
${SKILL_DIR}/apis/ by default (defaults to its own directory), regardless of the current working directory.
- The script must run in "a Python environment with RAPIDS installed" and requires the CUDA driver to be visible.
What Not to Do
- Do not proactively enable
cudf.pandas / cuml.accel — monkey-patches affect the entire Python process, so let the user decide whether to turn it on; your job is only to inform them of the activation command.
- Do not write try/except defenses for "is GPU available"; the presence of RAPIDS packages itself assumes CUDA is available — if the environment is missing, let it fail-fast.
- Do not convert every numpy / pandas call to cupy / cudf — in rewrite scenarios, the user may only want to accelerate the hot path; check the context before deciding the scope of refactoring.
- Do not write RAPIDS API signatures from memory; first
rg once into ${SKILL_DIR}/apis/<pkg>.txt to get the accurate signature (cuml and sklearn often differ in n_init, output_type, keyword-only parameters, etc.).
- Do not let data downlink mid-stream; place
cudf.DataFrame.to_pandas() only at the very end of the pipeline or at necessary integration points (plotting, writing compatible output formats, etc.).