hands-on labon a real NVIDIA GPU

GPU-Accelerated Data Science with RAPIDS

Rewrite a pandas + sklearn data-science pipeline on GPU using cuDF and cuML, benchmark each stage against the CPU baseline, and run an end-to-end filter -> feature-engineer -> predict pipeline that never leaves the GPU.

time
40 min
checked steps
4
level
Intermediate
setup
None
NCP-ADSNCA-GENL

$29.99a month with Pro

Pro opens this lab and every other one. Cancel any time.

Lab cockpit40 min · 4 stepsSession running
3 / 4 steps passingEnd-to-end GPU pipeline · step 4 of 4
lab.ipynb

Step 4 — End-to-end GPU pipeline

The point of RAPIDS isn't individual fast ops — it's that you can build a whole pipeline that never leaves the GPU.

[ ]:
# Full pipeline: filter -> feature eng -> score -> attach predictions# Everything stays on GPU. # 1. Filter high-value transactionslarge_txns = gdf[gdf['amount'] > gdf['amount'].quantile(0.75)] # 2. Engineer the same features we trained onlarge_txns = large_txns.assign(country_code=large_txns['country'].astype('category').cat.codes)feat_cols_g = ['user_id', 'product_id', 'country_code', 'amount']X_pred = large_txns[feat_cols_g].astype('float32').values   # still on GPU # 3. Score with the cuML model — same model object from Step 3cp.cuda.Stream.null.synchronize()t0 = time.perf_counter()preds_gpu = clf.predict(X_pred)cp.cuda.Stream.null.synchronize()pipe_ms = (time.perf_counter() - t0) * 1000 # 4. Attach predictions back to the dataframe — still on GPUresult_df = large_txns.assign(prediction=preds_gpu.astype('int32')) print(f'Pipeline scored {len(result_df):,} transactions in {pipe_ms:.0f} ms')print(f'Predicted refunds: {int(result_df["prediction"].sum()):,} of {len(result_df):,} ({100 * float(result_df["prediction"].mean()):.1f}%)')print()print('First few scored rows:')print(result_df[['user_id', 'country', 'amount', 'prediction']].head().to_pandas())
[ ]:
lab.check(4)
step 4 of 4, replayed: End-to-end GPU pipeline. The code, the output and the check are the lab's own.

4 steps, each checked when you finish it

A check runs your work at the end of every step. Hints and the full solution are there if you get stuck.

  1. 1

    cuDF vs pandas on a realistic groupby

    read this step below ↓

  2. 2

    cuML KMeans clustering

  3. 3

    cuML RandomForest vs sklearn RandomForest

  4. 4

    End-to-end GPU pipeline

Step 1 as it appears in the lab

The lab’s own text. The hint and the solution stay inside the lab.

Step 1: cuDF vs pandas on a realistic groupby

Every data scientist spends ~80% of their time on data munging and model iteration. RAPIDS, announced by NVIDIA in October 2018 at GTC, took the two most-used libraries in that workflow — pandas and scikit-learn — and reimplemented their APIs to run on GPU:

  • cuDF — pandas-compatible DataFrame, running on GPU via CUDA.
  • cuML — scikit-learn-compatible ML algorithms (KMeans, RandomForest, UMAP, t-SNE, ...) running on GPU.

The pitch: your existing pandas/sklearn code, with minimal changes, suddenly 10–100× faster.

What the real speedups look like

  • cuDF vs pandas: official benchmarks show up to 30× speedup on joins for a 10 GB dataset (NVIDIA T4).
  • cuML vs sklearn: 2–80× on typical tasks, up to 300× for UMAP on large datasets.
  • End-to-end workflows (load → preprocess → train): ~44× on an RTX 8000.

Important caveat: for small data, GPUs lose. The PCIe transfer overhead plus kernel launch latency dominates. The break-even point is typically around 100K rows for simple ops, 1M+ for complex joins. You'll see this yourself in this lab.

When RAPIDS is worth it

  • Large tabular datasets (millions of rows, wide features)
  • Dimensionality reduction (UMAP, t-SNE, PCA)
  • Classical ML where you currently wait 5+ min for sklearn to fit
  • Feature engineering at data-warehouse scale

When it's not

  • Tiny datasets (< 100K rows)
  • Deep learning (just use PyTorch directly — tensors already on GPU)
  • Graph analytics (use cuGraph instead of cuML for that)

Read while this runs


Groupby aggregation is the workhorse of data prep. We'll generate a synthetic e-commerce-ish dataset (1 million rows) and time the same groupby-aggregate on both pandas and cuDF.

Two things to notice:

  1. The cuDF API is nearly identical to pandas. You import cudf as pd and most code ports without changes.
  2. For a million rows on a 3060 Ti, cuDF should be roughly 5–15× faster than pandas on this op. The exact speedup varies by op; joins see larger gains than element-wise ops.
import numpy as np
import pandas as pd
import cudf
import time

# Build a realistic-ish 1M-row dataset (e-commerce events)
N = 1_000_000
rng = np.random.default_rng(42)
data = {
    'user_id': rng.integers(0, 50_000, N),
    'product_id': rng.integers(0, 2_000, N),
    'country': rng.choice(['US', 'UK', 'DE', 'FR', 'JP', 'IN', 'BR'], N),
    'amount': rng.exponential(scale=40, size=N).astype('float32'),
    'is_refund': (rng.random(N) < 0.03).astype('int8'),
}
pdf = pd.DataFrame(data)        # host/CPU dataframe
gdf = cudf.from_pandas(pdf)    # device/GPU dataframe (modern cuDF API)

print(f'Rows: {len(gdf):,}  |  Columns: {list(gdf.columns)}')
print(f'GPU memory used by dataframe: ~{gdf.memory_usage().sum() / 1e6:.1f} MB')
print()
print(gdf.head())
# Same query on pandas and cuDF — identical syntax
def run_groupby(df):
    return (
        df.groupby(['country', 'is_refund'])
          .agg({'amount': ['sum', 'mean', 'count']})
          .sort_values(('amount', 'sum'), ascending=False)
    )

# pandas timing
_ = run_groupby(pdf)  # warmup caches
t0 = time.perf_counter(); _ = run_groupby(pdf); pandas_ms = (time.perf_counter() - t0) * 1000

# cuDF timing
_ = run_groupby(gdf)  # warmup JIT / kernel compile
import cupy
cupy.cuda.Stream.null.synchronize()
t0 = time.perf_counter()
groupby_result = run_groupby(gdf)
cupy.cuda.Stream.null.synchronize()
cudf_ms = (time.perf_counter() - t0) * 1000

print(f'pandas:  {pandas_ms:6.1f} ms')
print(f'cuDF:    {cudf_ms:6.1f} ms')
print(f'Speedup: {pandas_ms / max(cudf_ms, 0.01):.1f}x')
print()
print('cuDF result:')
print(groupby_result.head())

what you get

  • A JupyterLab notebook on a real NVIDIA GPU
  • A check at the end of every step
  • Hints and the full solution
  • Nothing to install or configure

before you start

  • Comfortable with pandas DataFrames
  • Familiarity with scikit-learn model training basics
  • Understanding of groupby / feature engineering

Exam domains covered

GPU Acceleration & Distributed TrainingData Engineering & Preparation

Part of the GPU and Kubernetes track

Lab 19 of 20. GPU scheduling on your own Kubernetes cluster, the GPU Operator, CUDA kernels and profiling.

Questions about this lab

How much speedup should I actually expect from cuDF vs pandas?

Workload-dependent, but on a groupby over a million-row table you typically see 10-100x. The gap widens with larger tables and aggregations that benefit from GPU parallelism (joins, wide groupbys, string operations) and narrows on small data where PCIe transfer cost dominates or on operations cuDF hasn't specialized. The lab prints your measured speedup at the end of Step 1, treat it as a datapoint, not a guarantee, and always benchmark on your own workload before claiming a production win.

Can I swap cuDF for Polars, they're both faster than pandas?

Different tools for different problems. Polars is a CPU-multithreaded DataFrame library with a lazy query planner, brilliant for wrangling data that fits in RAM on a beefy CPU box. cuDF is a GPU DataFrame that compiles a subset of pandas operations onto CUDA, the advantage is decisive when your data lives next to a GPU model and you want zero-copy hand-off to cuML, cuGraph, or PyTorch. If your pipeline ends at a CSV, Polars often wins. If it ends at a GPU model, cuDF keeps the data on the device where it belongs.

Why does the lab care so much about avoiding .to_pandas() mid-pipeline?

Because every round-trip is a PCIe copy, gigabytes moving from GPU VRAM to system RAM and back. It's the single most common way RAPIDS pipelines silently lose their speedup. Step 4's grader checks isinstance(result_df, cudf.DataFrame) specifically because a pipeline that runs filter on GPU → .to_pandas() for feature engineering → back to GPU for prediction will produce correct numbers and terrible throughput. Once the data is on the device, keep it there until the very end.

Why does cuML RandomForest's accuracy differ slightly from sklearn's?

Same algorithm, different implementation, cuML builds trees with a histogram-based GPU algorithm that's numerically close to but not identical to sklearn's exact split-finding. On clean synthetic data they match within a percent or two; on messy real data they can diverge more. The 15-point bound in Step 3 is a sanity check that you're not comparing apples to oranges (wrong features, wrong train/test split) rather than an expectation of exact parity.

The model predicted all zeros on the refund data, is it broken?

Probably not. With a ~3% positive rate and the default 0.5 threshold, predicting the majority class is the Bayes-optimal constant predictor for maximizing accuracy, the model hasn't failed, the metric has. Fix it at the pipeline level: tune the decision threshold, use class_weight='balanced', oversample the minority class with SMOTE, or switch the primary metric to PR-AUC or recall@k. The reflection step pushes you to name the right fix rather than staring at the accuracy number.

Do I need a specific GPU to run this lab?

No, the lab provisions a real NVIDIA GPU pod on demand with RAPIDS preinstalled against a compatible CUDA version. You only need a browser. For your own machine, RAPIDS documents the supported SKUs and CUDA matrix, most Pascal-or-newer consumer and datacenter cards work, and the RAPIDS install instructions pin the right conda/pip channel for your driver version.

What you'll build in this RAPIDS / cuDF / cuML lab

RAPIDS is how data scientists stop throwing work over the wall to ML engineers in 2026, cuDF and cuML keep the pandas + sklearn API you already know but run on GPU, which means the same feature engineering that ends up feeding a PyTorch model can live on the device the model will train on with no round-trip. In about 40 minutes on a real NVIDIA GPU we provision, you'll port a pandas groupby and a sklearn RandomForest workflow to cuDF and cuML, measure the speedup yourself at each stage (typically 10-100x on a million-row groupby, narrower on classification), and walk away with a working mental model of when GPU data science actually pays off, and the specific anti-patterns (.to_pandas() mid-pipeline) that silently kill the win.

The substance is the cost model of GPU data science. You'll build a 1M+-row synthetic e-commerce events table, run the identical groupby-aggregate in pandas and cuDF with the same API surface, then fit cuml.KMeans on features materialised directly from the cuDF DataFrame (no pandas detour) and train a cuml.RandomForestClassifier alongside sklearn on a 200k-row subset so you can see both wall-clock times and accuracy within the expected ~15-point bound (same algorithm, different histogram-based GPU split-finding). The deliberately tricky step is the end-to-end pipeline: a ~3% refund rate means a default-threshold RF often predicts all zeros and still scores ~97%, that's the Bayes-optimal constant predictor given the imbalance, not a broken model, which is the exact gap between an ML engineer who reads metrics and one who fixes pipelines (threshold tuning, class weights, PR-AUC or recall@k as the primary metric). The grader enforces isinstance(result_df, cudf.DataFrame) at the end specifically to catch the .to_pandas() anti-pattern that shows up in almost every RAPIDS pipeline review, because PCIe round-trips dwarf compute and a single silent conversion can eat a 50x speedup without changing any numbers.

Prerequisites: comfort with pandas DataFrames, sklearn basics, and groupby-style feature engineering. The sandbox has cuDF, cuML, pandas, sklearn, and cupy preinstalled with CUDA versions matched to RAPIDS. Search-intent hooks: "pandas to cuDF migration", "cuML vs sklearn RandomForest", "RAPIDS GPU speedup benchmark", "when to use cuDF vs Polars", "GPU data science pipeline", the lab answers each with real timings and the specific failure modes you'll hit. Same pipeline code scales to tens of millions of rows on a single A100/H100 before you'd reach for Dask-cuDF.