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.
This skill covers four packages: cupy, cupyx, cudf, cuml.
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 / umap-learn / hdbscan / faiss 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 cuml.neighbors.NearestNeighbors over faiss / sklearn.neighbors; for sparse iterative solves → directly choose cupyx.scipy.sparse.linalg over scipy.sparse.linalg.
- Creating new scripts (ETL / training / inference / batch processing): use
cudf.read_csv / cudf.read_parquet / cudf.read_orc / cudf.read_json for data loading, cupy for matrix computation, cudf + cuml.preprocessing for feature engineering, cuml for model training and prediction, cupyx.scipy.ndimage / cupyx.scipy.signal for image-style filtering and signal processing.
- Adding new tests: use RAPIDS as much as possible for test data generation and assertion comparison (
cudf.testing / cuml.testing / cupy.testing, plus cuml.datasets.make_blobs and cupy.random for synthetic data); 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: this stack ships two monkey-patch accelerators.
cudf.pandas covers pandas, and cuml.accel covers scikit-learn and umap-learn (plus the standalone hdbscan package when the user has installed it). Both let existing CPU code 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's SciPy mirror and extensions (replaces scipy)
├── cudf.txt # cuDF (replaces pandas)
└── cuml.txt # cuML (replaces scikit-learn / umap-learn / hdbscan / statsmodels.tsa)
${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 # adjust to your install
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, from scipy import sparse, import faiss / hnswlib / annoy / umap / hdbscan, 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"
pandas cudf cudf.pandas zero-code via `python -m cudf.pandas script.py` or first-cell `%load_ext cudf.pandas`; or `import cudf.pandas; cudf.pandas.install()` BEFORE `import pandas`
$ rg -P '^sklearn\.cluster\t' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"
sklearn.cluster cuml.cluster cuml.accel accel proxies KMeans, DBSCAN, SpectralClustering; HDBSCAN and AgglomerativeClustering need an explicit cuml.cluster import
$ rg -P '^scipy\.ndimage\t' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"
scipy.ndimage cupyx.scipy.ndimage - -
$ rg -P '^sklearn\.neighbors\t' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"
sklearn.neighbors cuml.neighbors cuml.accel NearestNeighbors, KNeighborsClassifier, KNeighborsRegressor, KernelDensity
$ rg -i 'faiss' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"
faiss cuml.neighbors.NearestNeighbors - exact and IVF search via algorithm='brute'/'ivfflat'/'ivfpq' ('rbc' is limited to <=3 dims); this stack ships no graph index (CAGRA/HNSW)
faiss.Kmeans cuml.cluster.KMeans - GPU k-means with scalable-k-means++ init
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.pyJupyter first cell: %load_ext cudf.pandasIn-program (must precede import pandas): import cudf.pandas; cudf.pandas.install() |
cuml.accel |
CLI: python -m cuml.accel script.pyJupyter first cell: %load_ext cuml.accelEnvironment variable: CUML_ACCEL_ENABLED=1In-program (must precede import sklearn / umap / hdbscan): from cuml.accel import install; install() |
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.
cuml.accel proxies a fixed estimator list: sklearn.cluster KMeans / DBSCAN / SpectralClustering, sklearn.covariance EmpiricalCovariance / LedoitWolf, sklearn.decomposition PCA / IncrementalPCA / TruncatedSVD, sklearn.ensemble RandomForestClassifier / RandomForestRegressor, sklearn.kernel_ridge KernelRidge, sklearn.linear_model LinearRegression / LogisticRegression / Ridge / Lasso / ElasticNet, sklearn.manifold TSNE / SpectralEmbedding, sklearn.neighbors NearestNeighbors / KNeighborsClassifier / KNeighborsRegressor / KernelDensity, sklearn.preprocessing StandardScaler / MinMaxScaler / MaxAbsScaler / LabelEncoder / LabelBinarizer / PolynomialFeatures / TargetEncoder, sklearn.svm SVC / SVR / LinearSVC / LinearSVR, umap.UMAP, and hdbscan.HDBSCAN when the standalone hdbscan package is installed. sklearn.compose, sklearn.pipeline and sklearn.utils are patched for interoperability rather than proxied. Anything outside that list silently stays on CPU — sklearn.naive_bayes, sklearn.metrics, sklearn.feature_extraction, sklearn.random_projection, sklearn.model_selection and sklearn.multiclass have explicit cuml counterparts but no zero-code path, so reach them through an explicit import (Step 4).
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 → cuml.neighbors.NearestNeighbors.
Search for the target API in ${SKILL_DIR}/apis/<pkg>.txt using ripgrep. The four shipped index files are cupy.txt, cupyx.txt, cudf.txt and cuml.txt:
# Search for a class: exact match of class name followed by ( or .
rg -i '\.KMeans\(' "$SKILL_DIR/apis/cuml.txt"
rg -i '^cuml\.cluster\.KMeans' "$SKILL_DIR/apis/cuml.txt"
# Search for a function: similarly match .funcname(
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"
# Search for all APIs under a given submodule
rg '^cuml\.cluster\.' "$SKILL_DIR/apis/cuml.txt"
rg '^cupyx\.scipy\.ndimage\.' "$SKILL_DIR/apis/cupyx.txt"
# Search multiple files at once
rg -i '\.PCA\(' "$SKILL_DIR/apis/cuml.txt" "$SKILL_DIR/apis/cupyx.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.DataFrame.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.
- cuDF 26.08 tracks pandas 3.0:
applymap, backfill, pad, first, last and values_host are gone on both sides, as are the Index.is_boolean / is_categorical / is_floating / is_integer / is_interval / is_numeric dtype predicates. Write map, bfill, ffill and label slicing instead, and reach for cudf.api.types.is_dtype_obj_numeric / is_dtype_obj_string for dtype checks. DataFrame.flags / set_flags and Series.set_flags are new in this release.
- Default dtypes: some cuDF / cuML operators default to
float32, while sklearn defaults to float64. For high-precision comparisons, explicitly pass dtype="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 accel proxies KMeans, DBSCAN, SpectralClustering; HDBSCAN and AgglomerativeClustering need an explicit cuml.cluster import
- 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(*, n_clusters=8, max_iter=300, tol=0.0001, verbose=False, random_state=None, init='scalable-k-means++', n_init='auto', oversampling_factor=2.0, max_samples_per_batch=32768, device_buffer_samples=0, init_size=0, output_type=None) - KMeans is a basic but powerful clustering method which is optimized via
Per the signature, change directly to from cuml.cluster import KMeans. Note the defaults that differ from sklearn: init='scalable-k-means++', and every parameter is keyword-only.
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 on GPU — not
sklearn.datasets / numpy.random:$ rg -i '^cuml\.datasets\.make_blobs\(' "$SKILL_DIR/apis/cuml.txt"
cuml.datasets.make_blobs(n_samples=100, n_features=2, centers=None, cluster_std=1.0, center_box=(-10.0, 10.0), shuffle=True, random_state=None, return_centers=False, order='F', dtype='float32') - Generate isotropic Gaussian blobs for clustering.
dtype='float32' is the default here, so pass dtype='float64' when the test compares against a float64 sklearn baseline.
- Run on GPU:
$ rg -i '^cuml\.decomposition\.PCA\(' "$SKILL_DIR/apis/cuml.txt"
cuml.decomposition.PCA(*, copy=True, iterated_power=15, n_components=None, svd_solver='auto', tol=1e-07, verbose=False, whiten=False, output_type=None) - PCA (Principal Component Analysis) is a fundamental dimensionality
cuml.decomposition.PCA(n_components=k).fit_transform(X); n_components is keyword-only, unlike sklearn's positional first argument.
- CPU comparison: one-shot downlink with
X.get() → sklearn.decomposition.PCA(...).fit_transform(X_cpu).
- Assertion:
$ rg -i '^cupy\.testing\.assert_allclose\(' "$SKILL_DIR/apis/cupy.txt"
cupy.testing.assert_allclose(actual, desired, rtol=1e-07, atol=0, equal_nan=True, err_msg='', verbose=True, *, strict=False) - Raises an AssertionError if objects are not equal up to desired tolerance.
Compare the absolute values of components, since PCA sign is arbitrary. For dataframe results use cudf.testing.assert_frame_equal.
- Across the whole process, data transfer happens only once, at step 3.
Example 4: Replace faiss for nearest-neighbor search (rewrite scenario)
$ rg -i 'faiss' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"
faiss cuml.neighbors.NearestNeighbors - exact and IVF search via algorithm='brute'/'ivfflat'/'ivfpq' ('rbc' is limited to <=3 dims); this stack ships no graph index (CAGRA/HNSW)
faiss.Kmeans cuml.cluster.KMeans - GPU k-means with scalable-k-means++ init
$ rg -i '^cuml\.neighbors\.NearestNeighbors[(.]' "$SKILL_DIR/apis/cuml.txt"
cuml.neighbors.NearestNeighbors(*, n_neighbors=5, radius=1.0, algorithm='auto', metric='euclidean', p=2, algo_params=None, metric_params=None, n_jobs=None, verbose=False, output_type=None) - NearestNeighbors is an queries neighborhoods from a given set of
cuml.neighbors.NearestNeighbors.as_sklearn(self) - Convert this estimator into an equivalent scikit-learn (or scikit-learn
cuml.neighbors.NearestNeighbors.fit(self, X, y=None, *, convert_dtype='deprecated') -> "'NearestNeighbors'" - NeighborsBase.fit(self, X, y=None, *, convert_dtype='deprecated') -> 'NearestNeighbors'
cuml.neighbors.NearestNeighbors.from_sklearn(model) - Create a cuml estimator from a scikit-learn estimator.
cuml.neighbors.NearestNeighbors.get_params(self, deep=True) - Returns a dict of all params owned by this class. If the child class
cuml.neighbors.NearestNeighbors.kneighbors(self, X=None, n_neighbors=None, return_distance=True, *, convert_dtype='deprecated', two_pass_precision=False) - NeighborsBase.kneighbors(self, X=None, n_neighbors=None, return_distance=True, *, convert_dtype='deprecated', two_pass_precision=False)
cuml.neighbors.NearestNeighbors.kneighbors_graph(self, X=None, n_neighbors=None, mode='connectivity') - NeighborsBase.kneighbors_graph(self, X=None, n_neighbors=None, mode='connectivity')
cuml.neighbors.NearestNeighbors.radius_neighbors_graph(self, X=None, radius=None) - NearestNeighbors.radius_neighbors_graph(self, X=None, radius=None)
cuml.neighbors.NearestNeighbors.set_nvtx_annotations(self)
cuml.neighbors.NearestNeighbors.set_params(self, **params) - Accepts a dict of params and updates the corresponding ones owned by
Rewrite per the signatures: fit(X) ingests the corpus, kneighbors(Q, n_neighbors=k) returns (distances, indices). Pick the index through algorithm: 'brute' for exact search, 'ivfflat' / 'ivfpq' for the approximate IVF variants (tune them via algo_params), 'rbc' for the random ball cover, which only accepts 3 or fewer dimensions. This stack ships no graph index, so a faiss HNSW or a CAGRA index has no direct counterpart — say so and pick the closest IVF configuration instead. faiss.Kmeans maps to cuml.cluster.KMeans.
When RAPIDS Has No Corresponding Implementation
- Know the edges of this stack. It ships
cupy, cupyx, cudf and cuml only. Tell the user plainly rather than inventing an API for: graph algorithms (no cugraph, no nx-cugraph backend), image and whole-slide file I/O (no cucim; cupyx.scipy.ndimage covers filtering, morphology, geometric transforms and labelling, but no image decoding or slide readers), ANN graph indexes (no cuvs; cuml.neighbors covers exact and IVF search), multi-GPU or out-of-core dataframes (no dask-cudf), dashboards (no cuxfilter), and GPU forest inference for XGBoost or LightGBM models (cuml.fil / cuml.ForestInference still imports but was deprecated upstream in 26.06, so do not target it in new code).
Otherwise 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 cuml.model_selection.train_test_split / KFold plus a handwritten loop; sklearn's Pipeline is also absent in cuml but can be chained manually, and cuml.compose.ColumnTransformer covers the column-routing half).
- Check neighboring packages: an operator missing from
cuml or cudf can often be assembled by hand from cupy and cupyx.scipy primitives.
- 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. The script defaults to the four packages this skill covers: cupy, cupyx, cudf, cuml.
- 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.).
- Do not name a package this stack does not ship (
cugraph, nx_cugraph, cucim, cuvs, dask_cudf, cuxfilter); an import that fails is worse than an honest "no GPU path here".
1---2name: rapids-first3description: Use only when the user explicitly mentions "rapids-first" or "RAPIDS first", to prefer the RAPIDS GPU stack (cudf, cuml, cupy, cupyx) and its zero-code accelerators over pandas, scikit-learn, numpy and scipy when writing Python data-science, ML, ETL or numerical code.4---56# RAPIDS-first78## When to Enable910Enable 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.1112This skill covers four packages: `cupy`, `cupyx`, `cudf`, `cuml`.1314## Applicable Scenarios1516Once rapids-first is enabled, **all "writing Python code" subtasks** are handled according to the RAPIDS-first principle, not limited to rewriting existing CPU code:17181. **Rewriting existing CPU code**: replace numpy / pandas / sklearn / scipy / umap-learn / hdbscan / faiss calls with the corresponding RAPIDS APIs.192. **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 `cuml.neighbors.NearestNeighbors` over `faiss` / `sklearn.neighbors`; for sparse iterative solves → directly choose `cupyx.scipy.sparse.linalg` over `scipy.sparse.linalg`.203. **Creating new scripts (ETL / training / inference / batch processing)**: use `cudf.read_csv` / `cudf.read_parquet` / `cudf.read_orc` / `cudf.read_json` for data loading, `cupy` for matrix computation, `cudf` + `cuml.preprocessing` for feature engineering, `cuml` for model training and prediction, `cupyx.scipy.ndimage` / `cupyx.scipy.signal` for image-style filtering and signal processing.214. **Adding new tests**: use RAPIDS as much as possible for test data generation and assertion comparison (`cudf.testing` / `cuml.testing` / `cupy.testing`, plus `cuml.datasets.make_blobs` and `cupy.random` for synthetic data); let numpy / pandas appear only for RAPIDS↔CPU numerical comparisons, with a one-shot conversion at the boundary.225. **Fixing bugs**: even when just fixing a single numpy function call, if a suitable RAPIDS equivalent exists in the context, replace it in passing.2324In 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.2526## Three Core Principles27281. **Zero-code acceleration first**: this stack ships two monkey-patch accelerators. `cudf.pandas` covers pandas, and `cuml.accel` covers scikit-learn and umap-learn (plus the standalone hdbscan package when the user has installed it). Both let existing CPU code run on GPU without changing any imports. Use zero-code whenever possible — especially in the "rewriting existing CPU code" scenario.292. **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.303. **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.).3132## Directory Structure3334```35${SKILL_DIR}/36├── SKILL.md # This file37├── cpu-rapids-cuda-mapping.tsv # Index of CPU library → RAPIDS package (TSV)38├── fetch_rapids_apis.py # Script that (re-)generates apis/<pkg>.txt39└── apis/ # One API index file per package40 ├── cupy.txt # CuPy (replaces numpy)41 ├── cupyx.txt # CuPy's SciPy mirror and extensions (replaces scipy)42 ├── cudf.txt # cuDF (replaces pandas)43 └── cuml.txt # cuML (replaces scikit-learn / umap-learn / hdbscan / statsmodels.tsa)44```4546`${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`:4748```bash49SKILL_DIR=$HOME/.claude/skills/rapids-first # adjust to your install50```5152## Standard Workflow5354### Step 1: Identify the CPU library to replace/select5556- **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`, `from scipy import sparse`, `import faiss / hnswlib / annoy / umap / hdbscan`, etc.).57- **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.5859### Step 2: Use ripgrep on mapping.tsv to locate the RAPIDS package6061`cpu-rapids-cuda-mapping.tsv` fields:6263```64cpu_module rapids_target zero_code notes65```6667For an exact top-level package lookup use `^pkg\t`; for fuzzy submodule lookup use plain grep:6869```70$ rg -P '^pandas\t' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"71pandas cudf cudf.pandas zero-code via `python -m cudf.pandas script.py` or first-cell `%load_ext cudf.pandas`; or `import cudf.pandas; cudf.pandas.install()` BEFORE `import pandas`7273$ rg -P '^sklearn\.cluster\t' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"74sklearn.cluster cuml.cluster cuml.accel accel proxies KMeans, DBSCAN, SpectralClustering; HDBSCAN and AgglomerativeClustering need an explicit cuml.cluster import7576$ rg -P '^scipy\.ndimage\t' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"77scipy.ndimage cupyx.scipy.ndimage - -7879$ rg -P '^sklearn\.neighbors\t' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"80sklearn.neighbors cuml.neighbors cuml.accel NearestNeighbors, KNeighborsClassifier, KNeighborsRegressor, KernelDensity8182$ rg -i 'faiss' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"83faiss cuml.neighbors.NearestNeighbors - exact and IVF search via algorithm='brute'/'ivfflat'/'ivfpq' ('rbc' is limited to <=3 dims); this stack ships no graph index (CAGRA/HNSW)84faiss.Kmeans cuml.cluster.KMeans - GPU k-means with scalable-k-means++ init85```8687Read out `rapids_target` (target import path) and `zero_code` (zero-code solution identifier).8889### Step 3: Check whether the zero-code solution applies9091If the `zero_code` field is not `-` and the scenario allows it (primarily "rewriting existing CPU code"), prefer the zero-code solution. Canonical activation methods:9293| zero_code identifier | Activation method |94|---|---|95| `cudf.pandas` | CLI: `python -m cudf.pandas script.py`<br>Jupyter first cell: `%load_ext cudf.pandas`<br>In-program (must precede `import pandas`): `import cudf.pandas; cudf.pandas.install()` |96| `cuml.accel` | CLI: `python -m cuml.accel script.py`<br>Jupyter first cell: `%load_ext cuml.accel`<br>Environment variable: `CUML_ACCEL_ENABLED=1`<br>In-program (must precede `import sklearn` / `umap` / `hdbscan`): `from cuml.accel import install; install()` |9798Once 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.99100`cuml.accel` proxies a fixed estimator list: `sklearn.cluster` KMeans / DBSCAN / SpectralClustering, `sklearn.covariance` EmpiricalCovariance / LedoitWolf, `sklearn.decomposition` PCA / IncrementalPCA / TruncatedSVD, `sklearn.ensemble` RandomForestClassifier / RandomForestRegressor, `sklearn.kernel_ridge` KernelRidge, `sklearn.linear_model` LinearRegression / LogisticRegression / Ridge / Lasso / ElasticNet, `sklearn.manifold` TSNE / SpectralEmbedding, `sklearn.neighbors` NearestNeighbors / KNeighborsClassifier / KNeighborsRegressor / KernelDensity, `sklearn.preprocessing` StandardScaler / MinMaxScaler / MaxAbsScaler / LabelEncoder / LabelBinarizer / PolynomialFeatures / TargetEncoder, `sklearn.svm` SVC / SVR / LinearSVC / LinearSVR, `umap.UMAP`, and `hdbscan.HDBSCAN` when the standalone hdbscan package is installed. `sklearn.compose`, `sklearn.pipeline` and `sklearn.utils` are patched for interoperability rather than proxied. **Anything outside that list silently stays on CPU** — `sklearn.naive_bayes`, `sklearn.metrics`, `sklearn.feature_extraction`, `sklearn.random_projection`, `sklearn.model_selection` and `sklearn.multiclass` have explicit `cuml` counterparts but no zero-code path, so reach them through an explicit import (Step 4).101102**Applicability decision**:103- Rewrite scenario + user wants "to make existing code run faster" → zero-code104- New code / user wants explicit GPU APIs / user wants the code to clearly read as using RAPIDS → proceed to the explicit import in Step 4105106### Step 4: Explicit import replacement (when zero-code does not apply, or when writing new code)107108Two-step API location:1091101. Get `rapids_target` from mapping.tsv. Examples: `numpy → cupy`, `scipy.ndimage → cupyx.scipy.ndimage`, `sklearn.cluster → cuml.cluster`, `faiss → cuml.neighbors.NearestNeighbors`.1111122. Search for the target API in `${SKILL_DIR}/apis/<pkg>.txt` using `ripgrep`. The four shipped index files are `cupy.txt`, `cupyx.txt`, `cudf.txt` and `cuml.txt`:113114 ```bash115 # Search for a class: exact match of class name followed by ( or .116 rg -i '\.KMeans\(' "$SKILL_DIR/apis/cuml.txt"117 rg -i '^cuml\.cluster\.KMeans' "$SKILL_DIR/apis/cuml.txt"118119 # Search for a function: similarly match .funcname(120 rg -i '\.gaussian_filter\(' "$SKILL_DIR/apis/cupyx.txt"121 rg -i '\.read_parquet\(' "$SKILL_DIR/apis/cudf.txt"122 rg -i '\.fft2\(' "$SKILL_DIR/apis/cupy.txt"123124 # Search for all APIs under a given submodule125 rg '^cuml\.cluster\.' "$SKILL_DIR/apis/cuml.txt"126 rg '^cupyx\.scipy\.ndimage\.' "$SKILL_DIR/apis/cupyx.txt"127128 # Search multiple files at once129 rg -i '\.PCA\(' "$SKILL_DIR/apis/cuml.txt" "$SKILL_DIR/apis/cupyx.txt"130 ```131132 Each matched line is formatted as "full path(signature) - first line of docstring"; **copy the signature directly when writing code**.133134### Step 5: Respect the GPU data lifecycle135136- **Continuous GPU pipeline**: avoid mid-stream round-trips like `cudf.DataFrame.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.137- **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.138- **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.139- **cuDF 26.08 tracks pandas 3.0**: `applymap`, `backfill`, `pad`, `first`, `last` and `values_host` are gone on both sides, as are the `Index.is_boolean` / `is_categorical` / `is_floating` / `is_integer` / `is_interval` / `is_numeric` dtype predicates. Write `map`, `bfill`, `ffill` and label slicing instead, and reach for `cudf.api.types.is_dtype_obj_numeric` / `is_dtype_obj_string` for dtype checks. `DataFrame.flags` / `set_flags` and `Series.set_flags` are new in this release.140- **Default dtypes**: some cuDF / cuML operators default to `float32`, while sklearn defaults to `float64`. For high-precision comparisons, explicitly pass `dtype="float64"` or `convert_dtype=False`.141- **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.142143## Search Examples144145### Example 1: Accelerate `from sklearn.cluster import KMeans` to GPU (rewrite scenario)146147```148$ rg -P '^sklearn\.cluster\t' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"149sklearn.cluster cuml.cluster cuml.accel accel proxies KMeans, DBSCAN, SpectralClustering; HDBSCAN and AgglomerativeClustering need an explicit cuml.cluster import150```151152- **Zero-code preferred**: have the user run with `python -m cuml.accel script.py`; the original code stays untouched.153- **Explicit replacement** (if the user prefers):154 ```155 $ rg -i '^cuml\.cluster\.KMeans\(' "$SKILL_DIR/apis/cuml.txt"156 cuml.cluster.KMeans(*, n_clusters=8, max_iter=300, tol=0.0001, verbose=False, random_state=None, init='scalable-k-means++', n_init='auto', oversampling_factor=2.0, max_samples_per_batch=32768, device_buffer_samples=0, init_size=0, output_type=None) - KMeans is a basic but powerful clustering method which is optimized via157 ```158 Per the signature, change directly to `from cuml.cluster import KMeans`. Note the defaults that differ from sklearn: `init='scalable-k-means++'`, and every parameter is keyword-only.159160### Example 2: Implement a matrix FFT pipeline from scratch (new code scenario)161162Requirement: perform fft2 + magnitude + normalization on a batch of 2D matrices.1631641. "If using CPU, I would use numpy.fft + numpy" → look up the mapping:165 ```166 $ rg -P '^numpy(\.fft)?\t' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"167 numpy cupy - import cupy as cp; substitute cp for np168 numpy.fft cupy.fft - -169 ```1702. Look up the specific API:171 ```172 $ rg -i '\.fft2\(' "$SKILL_DIR/apis/cupy.txt"173 cupy.fft.fft2(a, s=None, axes=(-2, -1), norm=None) - Compute the two-dimensional FFT.174 ```1753. 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.176177### Example 3: Write unit tests for a new algorithm implementation (new test scenario)178179Requirement: test that a GPU-implemented PCA produces numerically consistent results with sklearn's PCA.1801811. Data generation on GPU — **not** `sklearn.datasets` / `numpy.random`:182 ```183 $ rg -i '^cuml\.datasets\.make_blobs\(' "$SKILL_DIR/apis/cuml.txt"184 cuml.datasets.make_blobs(n_samples=100, n_features=2, centers=None, cluster_std=1.0, center_box=(-10.0, 10.0), shuffle=True, random_state=None, return_centers=False, order='F', dtype='float32') - Generate isotropic Gaussian blobs for clustering.185 ```186 `dtype='float32'` is the default here, so pass `dtype='float64'` when the test compares against a float64 sklearn baseline.1872. Run on GPU:188 ```189 $ rg -i '^cuml\.decomposition\.PCA\(' "$SKILL_DIR/apis/cuml.txt"190 cuml.decomposition.PCA(*, copy=True, iterated_power=15, n_components=None, svd_solver='auto', tol=1e-07, verbose=False, whiten=False, output_type=None) - PCA (Principal Component Analysis) is a fundamental dimensionality191 ```192 `cuml.decomposition.PCA(n_components=k).fit_transform(X)`; `n_components` is keyword-only, unlike sklearn's positional first argument.1933. CPU comparison: one-shot downlink with `X.get()` → `sklearn.decomposition.PCA(...).fit_transform(X_cpu)`.1944. Assertion:195 ```196 $ rg -i '^cupy\.testing\.assert_allclose\(' "$SKILL_DIR/apis/cupy.txt"197 cupy.testing.assert_allclose(actual, desired, rtol=1e-07, atol=0, equal_nan=True, err_msg='', verbose=True, *, strict=False) - Raises an AssertionError if objects are not equal up to desired tolerance.198 ```199 Compare the absolute values of components, since PCA sign is arbitrary. For dataframe results use `cudf.testing.assert_frame_equal`.2005. Across the whole process, data transfer happens only once, at step 3.201202### Example 4: Replace faiss for nearest-neighbor search (rewrite scenario)203204```205$ rg -i 'faiss' "$SKILL_DIR/cpu-rapids-cuda-mapping.tsv"206faiss cuml.neighbors.NearestNeighbors - exact and IVF search via algorithm='brute'/'ivfflat'/'ivfpq' ('rbc' is limited to <=3 dims); this stack ships no graph index (CAGRA/HNSW)207faiss.Kmeans cuml.cluster.KMeans - GPU k-means with scalable-k-means++ init208209$ rg -i '^cuml\.neighbors\.NearestNeighbors[(.]' "$SKILL_DIR/apis/cuml.txt"210cuml.neighbors.NearestNeighbors(*, n_neighbors=5, radius=1.0, algorithm='auto', metric='euclidean', p=2, algo_params=None, metric_params=None, n_jobs=None, verbose=False, output_type=None) - NearestNeighbors is an queries neighborhoods from a given set of211cuml.neighbors.NearestNeighbors.as_sklearn(self) - Convert this estimator into an equivalent scikit-learn (or scikit-learn212cuml.neighbors.NearestNeighbors.fit(self, X, y=None, *, convert_dtype='deprecated') -> "'NearestNeighbors'" - NeighborsBase.fit(self, X, y=None, *, convert_dtype='deprecated') -> 'NearestNeighbors'213cuml.neighbors.NearestNeighbors.from_sklearn(model) - Create a cuml estimator from a scikit-learn estimator.214cuml.neighbors.NearestNeighbors.get_params(self, deep=True) - Returns a dict of all params owned by this class. If the child class215cuml.neighbors.NearestNeighbors.kneighbors(self, X=None, n_neighbors=None, return_distance=True, *, convert_dtype='deprecated', two_pass_precision=False) - NeighborsBase.kneighbors(self, X=None, n_neighbors=None, return_distance=True, *, convert_dtype='deprecated', two_pass_precision=False)216cuml.neighbors.NearestNeighbors.kneighbors_graph(self, X=None, n_neighbors=None, mode='connectivity') - NeighborsBase.kneighbors_graph(self, X=None, n_neighbors=None, mode='connectivity')217cuml.neighbors.NearestNeighbors.radius_neighbors_graph(self, X=None, radius=None) - NearestNeighbors.radius_neighbors_graph(self, X=None, radius=None)218cuml.neighbors.NearestNeighbors.set_nvtx_annotations(self)219cuml.neighbors.NearestNeighbors.set_params(self, **params) - Accepts a dict of params and updates the corresponding ones owned by220```221222Rewrite per the signatures: `fit(X)` ingests the corpus, `kneighbors(Q, n_neighbors=k)` returns `(distances, indices)`. Pick the index through `algorithm`: `'brute'` for exact search, `'ivfflat'` / `'ivfpq'` for the approximate IVF variants (tune them via `algo_params`), `'rbc'` for the random ball cover, which only accepts 3 or fewer dimensions. This stack ships no graph index, so a faiss HNSW or a CAGRA index has no direct counterpart — say so and pick the closest IVF configuration instead. `faiss.Kmeans` maps to `cuml.cluster.KMeans`.223224## When RAPIDS Has No Corresponding Implementation225226- **Know the edges of this stack.** It ships `cupy`, `cupyx`, `cudf` and `cuml` only. Tell the user plainly rather than inventing an API for: graph algorithms (no `cugraph`, no `nx-cugraph` backend), image and whole-slide file I/O (no `cucim`; `cupyx.scipy.ndimage` covers filtering, morphology, geometric transforms and labelling, but no image decoding or slide readers), ANN graph indexes (no `cuvs`; `cuml.neighbors` covers exact and IVF search), multi-GPU or out-of-core dataframes (no `dask-cudf`), dashboards (no `cuxfilter`), and GPU forest inference for XGBoost or LightGBM models (`cuml.fil` / `cuml.ForestInference` still imports but was deprecated upstream in 26.06, so do not target it in new code).227228Otherwise handle by priority:2292301. 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 `cuml.model_selection.train_test_split` / `KFold` plus a handwritten loop; sklearn's `Pipeline` is also absent in cuml but can be chained manually, and `cuml.compose.ColumnTransformer` covers the column-routing half).2312. Check neighboring packages: an operator missing from `cuml` or `cudf` can often be assembled by hand from `cupy` and `cupyx.scipy` primitives.2323. 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).233234## (Re-)Generating apis/235236- After any RAPIDS package upgrade, run `python "$SKILL_DIR/fetch_rapids_apis.py"` to regenerate `${SKILL_DIR}/apis/<pkg>.txt`. The script defaults to the four packages this skill covers: `cupy`, `cupyx`, `cudf`, `cuml`.237- You can also target specific packages only: `python "$SKILL_DIR/fetch_rapids_apis.py" --packages cudf cuml`.238- The script writes back to `${SKILL_DIR}/apis/` by default (defaults to its own directory), regardless of the current working directory.239- The script must run in "a Python environment with RAPIDS installed" and requires the CUDA driver to be visible.240241## What Not to Do242243- **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.244- **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.245- **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.246- **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.).247- **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.).248- **Do not name a package this stack does not ship** (`cugraph`, `nx_cugraph`, `cucim`, `cuvs`, `dask_cudf`, `cuxfilter`); an import that fails is worse than an honest "no GPU path here".