Skill Tracer
Skills › Scientific Agent Skills › optimize-for-gpu › cudf.md

cudf.md

769 lines · skills/optimize-for-gpu/references/cudf.md at 5f2bc03

Open on GitHub
1# cuDF Reference
2
3> Review: 2026-10-01. Code below is illustrative unless explicitly described as CPU-tested.
4> GPU execution, performance, GDS, and multi-GPU behavior require validation on target hardware.
5
6cuDF is a GPU DataFrame library that provides a pandas-like API for loading, joining, aggregating, filtering, and manipulating tabular data entirely on the GPU. It's part of the NVIDIA RAPIDS ecosystem and is built on the Apache Arrow columnar memory format.
7
8> **Full documentation:** https://docs.nvidia.com/cudf/26.08/
9

cuDF Reference

Review: 2026-10-01. Code below is illustrative unless explicitly described as CPU-tested. GPU execution, performance, GDS, and multi-GPU behavior require validation on target hardware.

cuDF is a GPU DataFrame library that provides a pandas-like API for loading, joining, aggregating, filtering, and manipulating tabular data entirely on the GPU. It's part of the NVIDIA RAPIDS ecosystem and is built on the Apache Arrow columnar memory format.

Full documentation: https://docs.nvidia.com/cudf/26.08/
Raw lines 1 to 9
10## Table of Contents
121. [Installation and Setup](#installation-and-setup)
132. [Two Usage Modes](#two-usage-modes)
143. [cudf.pandas Accelerator Mode](#cudfpandas-accelerator-mode)
154. [Core API: DataFrame and Series](#core-api)
165. [IO Operations](#io-operations)
176. [GroupBy Operations](#groupby-operations)
187. [String Operations](#string-operations)
198. [User Defined Functions (UDFs)](#user-defined-functions)
209. [Missing Data Handling](#missing-data-handling)
2110. [Data Types](#data-types)
2211. [Memory Management](#memory-management)
2312. [Interoperability](#interoperability)
2413. [Multi-GPU with Dask-cuDF](#multi-gpu-with-dask-cudf)
2514. [Performance Optimization](#performance-optimization)
2615. [Key Differences from pandas](#key-differences-from-pandas)
2716. [Common Migration Patterns](#common-migration-patterns)
29---

Table of Contents

  1. Installation and Setup
  2. Two Usage Modes
  3. cudf.pandas Accelerator Mode
  4. Core API: DataFrame and Series
  5. IO Operations
  6. GroupBy Operations
  7. String Operations
  8. User Defined Functions (UDFs)
  9. Missing Data Handling
  10. Data Types
  11. Memory Management
  12. Interoperability
  13. Multi-GPU with Dask-cuDF
  14. Performance Optimization
  15. Key Differences from pandas
  16. Common Migration Patterns

Raw lines 10 to 30
31## Installation and Setup
33Use `uv add` in standalone examples; follow the user's existing project package manager when one
34is already configured.
36```bash
37uv add "cudf-cu12==26.8.*" # For CUDA 12.x
38uv add "cudf-cu13==26.8.*" # For CUDA 13.x
39```
41cuDF wheels are now published directly to PyPI — the `--extra-index-url=https://pypi.nvidia.com` extra index is no longer required. Requires Python 3.11-3.14; 26.08 targets pandas 3, CuPy 14, and NumPy 2.
43Verify:
44```python
45import cudf
46print(cudf.Series([1, 2, 3])) # Should print a GPU series
47```
49---

Installation and Setup

Use uv add in standalone examples; follow the user's existing project package manager when one is already configured.

uv add "cudf-cu12==26.8.*"    # For CUDA 12.x
uv add "cudf-cu13==26.8.*"    # For CUDA 13.x

cuDF wheels are now published directly to PyPI — the --extra-index-url=https://pypi.nvidia.com extra index is no longer required. Requires Python 3.11-3.14; 26.08 targets pandas 3, CuPy 14, and NumPy 2.

Verify:

import cudf
print(cudf.Series([1, 2, 3]))  # Should print a GPU series

Raw lines 31 to 50
51## Two Usage Modes
53cuDF offers two ways to accelerate pandas code:

Two Usage Modes

cuDF offers two ways to accelerate pandas code:

Raw lines 51 to 54
55### 1. cudf.pandas (Zero-Code-Change)
56Drop-in replacement that automatically accelerates pandas. Falls back to CPU for unsupported operations. Best for: quick acceleration of existing code, mixed codebases, prototyping.

1. cudf.pandas (Zero-Code-Change)

Drop-in replacement that automatically accelerates pandas. Falls back to CPU for unsupported operations. Best for: quick acceleration of existing code, mixed codebases, prototyping.

Raw lines 55 to 57
58### 2. Direct cuDF API
59Replace `import pandas` with `import cudf`. Maximum performance, no proxy overhead, but requires adapting code to cuDF's API (which has some behavioral differences from pandas). Best for: production pipelines, maximum performance, new GPU-first code.
61---

2. Direct cuDF API

Replace import pandas with import cudf. Maximum performance, no proxy overhead, but requires adapting code to cuDF's API (which has some behavioral differences from pandas). Best for: production pipelines, maximum performance, new GPU-first code.


Raw lines 58 to 62
63## cudf.pandas Accelerator Mode
65The fastest path from pandas to GPU — no code changes required.

cudf.pandas Accelerator Mode

The fastest path from pandas to GPU — no code changes required.

Raw lines 63 to 66
67### Activation
69```python
70# Jupyter/IPython (MUST be before any pandas import)
71%load_ext cudf.pandas
72import pandas as pd # Now GPU-accelerated
74# Command line
75# python -m cudf.pandas your_script.py
76# python -m cudf.pandas --profile your_script.py # With profiling
78# Programmatic
79import cudf.pandas
80cudf.pandas.install()
81import pandas as pd # Now GPU-accelerated
82```
84**Critical:** If pandas was already imported in the session, you must restart the kernel/process.

Activation

# Jupyter/IPython (MUST be before any pandas import)
%load_ext cudf.pandas
import pandas as pd  # Now GPU-accelerated

# Command line
# python -m cudf.pandas your_script.py
# python -m cudf.pandas --profile your_script.py  # With profiling

# Programmatic
import cudf.pandas
cudf.pandas.install()
import pandas as pd  # Now GPU-accelerated

Critical: If pandas was already imported in the session, you must restart the kernel/process.

Raw lines 67 to 85
86### How It Works
88- `import pandas` returns a proxy module that wraps cuDF and pandas.
89- Every operation is first attempted on GPU (cuDF). If it fails, it automatically falls back to CPU (pandas).
90- Data transfers between GPU and CPU happen only when necessary.
91- Uses managed memory by default — can process datasets larger than GPU memory.
92- Validate the exact workload; aggregate upstream test pass rates do not establish API equivalence.

How It Works

  • import pandas returns a proxy module that wraps cuDF and pandas.
  • Every operation is first attempted on GPU (cuDF). If it fails, it automatically falls back to CPU (pandas).
  • Data transfers between GPU and CPU happen only when necessary.
  • Uses managed memory by default — can process datasets larger than GPU memory.
  • Validate the exact workload; aggregate upstream test pass rates do not establish API equivalence.
Raw lines 86 to 93
94### Profiling GPU vs CPU Execution
96```python
97%%cudf.pandas.profile # Shows GPU vs CPU operation breakdown per cell
98%%cudf.pandas.line_profile # Per-line GPU/CPU timing
99```

Profiling GPU vs CPU Execution

%%cudf.pandas.profile        # Shows GPU vs CPU operation breakdown per cell
%%cudf.pandas.line_profile   # Per-line GPU/CPU timing
Raw lines 94 to 100
101### Accessing Underlying Objects
103```python
104proxy_df.as_gpu_object() # Get the cuDF DataFrame directly
105proxy_df.as_cpu_object() # Get the pandas DataFrame directly
106```
108Note: automatic fallback stops working after you extract the underlying object.

Accessing Underlying Objects

proxy_df.as_gpu_object()  # Get the cuDF DataFrame directly
proxy_df.as_cpu_object()  # Get the pandas DataFrame directly

Note: automatic fallback stops working after you extract the underlying object.

Raw lines 101 to 109
110### Compatible Third-Party Libraries
111cuGraph, cuML, Hvplot, Holoview, Ibis, NumPy, Matplotlib, Plotly, PyTorch, Seaborn, Scikit-Learn, SciPy, TensorFlow, XGBoost.
113**Not compatible:** Joblib. For distributed work, use Dask-cuDF instead.

Compatible Third-Party Libraries

cuGraph, cuML, Hvplot, Holoview, Ibis, NumPy, Matplotlib, Plotly, PyTorch, Seaborn, Scikit-Learn, SciPy, TensorFlow, XGBoost.

Not compatible: Joblib. For distributed work, use Dask-cuDF instead.

Raw lines 110 to 114
115### Limitations
117- Join operations don't guarantee pandas' row ordering (for performance).
118- Cannot use `import cudf` alongside cudf.pandas in the same session.
119- Pickled objects are not interchangeable between regular pandas and cudf.pandas.
120- Proxy arrays subclass `numpy.ndarray`, which can cause eager device-to-host transfers.
121- To force CPU-only: set `CUDF_PANDAS_FALLBACK_MODE=1`.
123---

Limitations

  • Join operations don't guarantee pandas' row ordering (for performance).
  • Cannot use import cudf alongside cudf.pandas in the same session.
  • Pickled objects are not interchangeable between regular pandas and cudf.pandas.
  • Proxy arrays subclass numpy.ndarray, which can cause eager device-to-host transfers.
  • To force CPU-only: set CUDF_PANDAS_FALLBACK_MODE=1.

Raw lines 115 to 124
125## Core API
127### Creating DataFrames and Series
129```python
130import cudf
132# From dict
133df = cudf.DataFrame({"a": [1, 2, 3], "b": [4.0, 5.0, 6.0], "c": ["x", "y", "z"]})
135# From pandas
136import pandas as pd
137gdf = cudf.DataFrame.from_pandas(pd.DataFrame({"a": [1, 2, 3]}))
138# or
139gdf = cudf.DataFrame(pandas_df)
141# Series
142s = cudf.Series([1, 2, 3, None, 5])
144# Back to pandas
145pdf = gdf.to_pandas()
146```

Creating DataFrames and Series

import cudf

# From dict
df = cudf.DataFrame({"a": [1, 2, 3], "b": [4.0, 5.0, 6.0], "c": ["x", "y", "z"]})

# From pandas
import pandas as pd
gdf = cudf.DataFrame.from_pandas(pd.DataFrame({"a": [1, 2, 3]}))
# or
gdf = cudf.DataFrame(pandas_df)

# Series
s = cudf.Series([1, 2, 3, None, 5])

# Back to pandas
pdf = gdf.to_pandas()
Raw lines 127 to 147
148### Common Operations (Same as pandas)
150```python
151df.head(10)
152df.tail(5)
153df.describe()
154df.info()
155df.dtypes
156df.columns
157df.shape
159# Selection
160df["a"] # Column → Series
161df[["a", "b"]] # Multiple columns → DataFrame
162df.loc[2:5, ["a", "b"]] # Label-based indexing
163df.iloc[0:3] # Integer-based indexing
165# Filtering
166df[df["a"] > 2]
167df.query("a > 2 and b < 6") # Supports @var for local variables
169# Sorting
170df.sort_values("a", ascending=False)
171df.sort_index()
173# Missing data
174df.fillna(0)
175df.dropna()
176df.isna()
178# Aggregations
179df["a"].sum()
180df["a"].mean()
181df["a"].std()
182df["a"].value_counts()
184# Transforms
185df["a"].clip(lower=1, upper=5)
186df["a"].apply(lambda x: x * 2) # JIT-compiled
188# Combining
189cudf.concat([df1, df2])
190df1.merge(df2, on="key")
191df1.merge(df2, on="key", how="left") # left, right, inner, outer
193# Arrow interop (host/device transfer boundary)
194arrow_table = df.to_arrow()
195df = cudf.DataFrame.from_arrow(arrow_table)
196```
198---

Common Operations (Same as pandas)

df.head(10)
df.tail(5)
df.describe()
df.info()
df.dtypes
df.columns
df.shape

# Selection
df["a"]                     # Column → Series
df[["a", "b"]]             # Multiple columns → DataFrame
df.loc[2:5, ["a", "b"]]   # Label-based indexing
df.iloc[0:3]               # Integer-based indexing

# Filtering
df[df["a"] > 2]
df.query("a > 2 and b < 6")  # Supports @var for local variables

# Sorting
df.sort_values("a", ascending=False)
df.sort_index()

# Missing data
df.fillna(0)
df.dropna()
df.isna()

# Aggregations
df["a"].sum()
df["a"].mean()
df["a"].std()
df["a"].value_counts()

# Transforms
df["a"].clip(lower=1, upper=5)
df["a"].apply(lambda x: x * 2)  # JIT-compiled

# Combining
cudf.concat([df1, df2])
df1.merge(df2, on="key")
df1.merge(df2, on="key", how="left")  # left, right, inner, outer

# Arrow interop (host/device transfer boundary)
arrow_table = df.to_arrow()
df = cudf.DataFrame.from_arrow(arrow_table)

Raw lines 148 to 199
200## IO Operations
202GPU-accelerated file reading and writing — often dramatically faster than pandas for large files.

IO Operations

GPU-accelerated file reading and writing — often dramatically faster than pandas for large files.

Raw lines 200 to 203
204### Parquet (Recommended for Performance)
206```python
207# Read
208df = cudf.read_parquet("data.parquet")
209df = cudf.read_parquet("data.parquet", columns=["a", "b"]) # Read only specific columns
211# Write
212df.to_parquet("output.parquet")
214# Metadata inspection (without loading data)
215cudf.io.parquet.read_parquet_metadata("data.parquet")
217# Incremental writing
218writer = cudf.io.parquet.ParquetDatasetWriter("output_dir/", partition_cols=["year"])
219writer.write_table(df)
220writer.close()
221```

Parquet (Recommended for Performance)

# Read
df = cudf.read_parquet("data.parquet")
df = cudf.read_parquet("data.parquet", columns=["a", "b"])  # Read only specific columns

# Write
df.to_parquet("output.parquet")

# Metadata inspection (without loading data)
cudf.io.parquet.read_parquet_metadata("data.parquet")

# Incremental writing
writer = cudf.io.parquet.ParquetDatasetWriter("output_dir/", partition_cols=["year"])
writer.write_table(df)
writer.close()
Raw lines 204 to 222
223### CSV
225```python
226df = cudf.read_csv("data.csv")
227df = cudf.read_csv("data.csv", usecols=["a", "b"], dtype={"a": "int32"})
228df.to_csv("output.csv", index=False)
229```

CSV

df = cudf.read_csv("data.csv")
df = cudf.read_csv("data.csv", usecols=["a", "b"], dtype={"a": "int32"})
df.to_csv("output.csv", index=False)
Raw lines 223 to 230
231### JSON
233```python
234df = cudf.read_json("data.json")
235df = cudf.read_json("data.json", lines=True) # JSON Lines format
236df.to_json("output.json")
237```

JSON

df = cudf.read_json("data.json")
df = cudf.read_json("data.json", lines=True)  # JSON Lines format
df.to_json("output.json")
Raw lines 231 to 238
239### ORC
241```python
242df = cudf.read_orc("data.orc")
243df.to_orc("output.orc")
244```

ORC

df = cudf.read_orc("data.orc")
df.to_orc("output.orc")
Raw lines 239 to 245
246### Other Formats
248| Format | Read | Write | GPU-Accelerated |
249|--------|------|-------|-----------------|
250| Avro | `cudf.read_avro()` | N/A | Yes (read only) |
251| Text | `cudf.read_text()` | N/A | Yes (read only) |
252| HDF5 | `cudf.read_hdf()` | `df.to_hdf()` | No (uses pandas) |
253| Feather | `cudf.read_feather()` | `df.to_feather()` | No (uses pandas) |
255**Prefer Parquet over CSV** — columnar format reads faster on GPU, supports predicate pushdown, and compresses well.
257---

Other Formats

FormatReadWriteGPU-Accelerated
Avrocudf.read_avro()N/AYes (read only)
Textcudf.read_text()N/AYes (read only)
HDF5cudf.read_hdf()df.to_hdf()No (uses pandas)
Feathercudf.read_feather()df.to_feather()No (uses pandas)

Prefer Parquet over CSV — columnar format reads faster on GPU, supports predicate pushdown, and compresses well.


Raw lines 246 to 258
259## GroupBy Operations

GroupBy Operations

Raw lines 259 to 260
261### Basic GroupBy
263```python
264df.groupby("category").sum()
265df.groupby(["category", "subcategory"]).mean()
266df.groupby("category").agg({"value": "sum", "count": "max"})
267df.groupby("category").agg({"value": ["sum", "min", "max"], "count": "mean"})
268```

Basic GroupBy

df.groupby("category").sum()
df.groupby(["category", "subcategory"]).mean()
df.groupby("category").agg({"value": "sum", "count": "max"})
df.groupby("category").agg({"value": ["sum", "min", "max"], "count": "mean"})
Raw lines 261 to 269
270### Supported Aggregations
272**Universal:** `count`, `size`, `nunique`, `nth`, `collect`, `unique`
273**Numeric:** `sum`, `mean`, `var`, `std`, `median`, `idxmin`, `idxmax`, `min`, `max`, `quantile`
274**Specialized:** `corr`, `cov`

Supported Aggregations

Universal: count, size, nunique, nth, collect, unique Numeric: sum, mean, var, std, median, idxmin, idxmax, min, max, quantile Specialized: corr, cov

Raw lines 270 to 275
276### GroupBy Transform
278```python
279df.groupby("category").transform("max") # Broadcasts result to match group size
280```

GroupBy Transform

df.groupby("category").transform("max")  # Broadcasts result to match group size
Raw lines 276 to 281
282### GroupBy Apply
284```python
285df.groupby("category").apply(lambda x: x.max() - x.min())
286```
288**Warning:** Apply runs the function sequentially per group — can be slow with many small groups. Use vectorized aggregations whenever possible.

GroupBy Apply

df.groupby("category").apply(lambda x: x.max() - x.min())

Warning: Apply runs the function sequentially per group — can be slow with many small groups. Use vectorized aggregations whenever possible.

Raw lines 282 to 289
290### JIT-Compiled GroupBy (User-Defined Aggregation)
292```python
293def custom_agg(df):
294 return df["value"].max() - df["value"].min() / 2
296result = df.groupby("category").apply(custom_agg, engine="jit")
297```
299JIT restrictions: no nulls, only int32/64 and float32/64, cannot return new columns.

JIT-Compiled GroupBy (User-Defined Aggregation)

def custom_agg(df):
    return df["value"].max() - df["value"].min() / 2

result = df.groupby("category").apply(custom_agg, engine="jit")

JIT restrictions: no nulls, only int32/64 and float32/64, cannot return new columns.

Raw lines 290 to 300
301### Important: Sort Behavior
303cuDF uses `sort=False` by default (unlike pandas which sorts by default). To match pandas:
304```python
305df.groupby("category", sort=True).sum()
306# Or globally:
307cudf.set_option("mode.pandas_compatible", True)
308```
310---

Important: Sort Behavior

cuDF uses sort=False by default (unlike pandas which sorts by default). To match pandas:

df.groupby("category", sort=True).sum()
# Or globally:
cudf.set_option("mode.pandas_compatible", True)

Raw lines 301 to 311
312## String Operations
314cuDF provides GPU-accelerated string operations via the `.str` accessor — identical API to pandas.
316```python
317s = cudf.Series(["Hello World", "foo bar", "RAPIDS GPU", None])
319# Case
320s.str.lower()
321s.str.upper()
322s.str.title()
323s.str.capitalize()
325# Pattern matching
326s.str.contains("World")
327s.str.startswith("Hello")
328s.str.endswith("GPU")
329s.str.match(r"^[A-Z]")
331# Extraction and replacement
332s.str.extract(r"(\w+)\s(\w+)")
333s.str.replace("World", "GPU")
334s.str.slice(0, 5)
336# Splitting and joining
337s.str.split(" ")
338s.str.cat(sep=", ")
340# Info
341s.str.len()
342s.str.isalpha()
343s.str.isdigit()
345# cuDF-exclusive operations (not in pandas)
346s.str.normalize_spaces() # Collapse whitespace
347s.str.tokenize() # Tokenize strings
348s.str.ngrams(2) # Generate n-grams
349s.str.edit_distance(other) # Levenshtein distance
350s.str.url_encode()
351s.str.url_decode()
352```
354---

String Operations

cuDF provides GPU-accelerated string operations via the .str accessor — identical API to pandas.

s = cudf.Series(["Hello World", "foo bar", "RAPIDS GPU", None])

# Case
s.str.lower()
s.str.upper()
s.str.title()
s.str.capitalize()

# Pattern matching
s.str.contains("World")
s.str.startswith("Hello")
s.str.endswith("GPU")
s.str.match(r"^[A-Z]")

# Extraction and replacement
s.str.extract(r"(\w+)\s(\w+)")
s.str.replace("World", "GPU")
s.str.slice(0, 5)

# Splitting and joining
s.str.split(" ")
s.str.cat(sep=", ")

# Info
s.str.len()
s.str.isalpha()
s.str.isdigit()

# cuDF-exclusive operations (not in pandas)
s.str.normalize_spaces()   # Collapse whitespace
s.str.tokenize()           # Tokenize strings
s.str.ngrams(2)            # Generate n-grams
s.str.edit_distance(other) # Levenshtein distance
s.str.url_encode()
s.str.url_decode()

Raw lines 312 to 355
356## User Defined Functions

User Defined Functions

Raw lines 356 to 357
358### Series.apply() — JIT-Compiled
360```python
361s = cudf.Series([1, 2, 3, 4, 5])
363def square_plus_one(x):
364 return x ** 2 + 1
366s.apply(square_plus_one) # Compiled to GPU kernel via Numba
367```
369With arguments:
370```python
371def add_constant(x, c):
372 return x + c
374s.apply(add_constant, args=(42,))
375```

Series.apply() — JIT-Compiled

s = cudf.Series([1, 2, 3, 4, 5])

def square_plus_one(x):
    return x ** 2 + 1

s.apply(square_plus_one)  # Compiled to GPU kernel via Numba

With arguments:

def add_constant(x, c):
    return x + c

s.apply(add_constant, args=(42,))
Raw lines 358 to 376
377### DataFrame.apply() — Row-wise (axis=1)
379```python
380def row_func(row):
381 return row["a"] + row["b"] * 2
383df.apply(row_func, axis=1) # Access columns by name via dict-like syntax
384```

DataFrame.apply() — Row-wise (axis=1)

def row_func(row):
    return row["a"] + row["b"] * 2

df.apply(row_func, axis=1)  # Access columns by name via dict-like syntax
Raw lines 377 to 385
386### Null Handling in UDFs
388Nulls propagate automatically:
389```python
390s = cudf.Series([1, cudf.NA, 3])
391def f(x):
392 return x + 1
393s.apply(f) # Returns [2, <NA>, 4]
394```
396Explicit null checks:
397```python
398def f(x):
399 if x is cudf.NA:
400 return 0
401 return x + 1
402```

Null Handling in UDFs

Nulls propagate automatically:

s = cudf.Series([1, cudf.NA, 3])
def f(x):
    return x + 1
s.apply(f)  # Returns [2, <NA>, 4]

Explicit null checks:

def f(x):
    if x is cudf.NA:
        return 0
    return x + 1
Raw lines 386 to 403
404### String UDFs
406String operations inside UDFs support: `==`, `!=`, `>=`, `<=`, `startswith()`, `endswith()`, `find()`, `rfind()`, `count()`, `in`, `strip/lstrip/rstrip()`, `upper/lower()`, `replace()`, `+` (concatenation), `len()`, boolean checks.
408For string UDFs creating intermediate strings, allocate heap:
409```python
410from cudf.core.udf.utils import set_malloc_heap_size
411set_malloc_heap_size(int(2e9)) # 2 GB
412```

String UDFs

String operations inside UDFs support: ==, !=, >=, <=, startswith(), endswith(), find(), rfind(), count(), in, strip/lstrip/rstrip(), upper/lower(), replace(), + (concatenation), len(), boolean checks.

For string UDFs creating intermediate strings, allocate heap:

from cudf.core.udf.utils import set_malloc_heap_size
set_malloc_heap_size(int(2e9))  # 2 GB
Raw lines 404 to 413
414### Rolling Window UDFs
416```python
417import math
419s = cudf.Series([16, 25, 36, 49, 64, 81], dtype="float64")
421def max_sqrt(window):
422 result = 0
423 for val in window:
424 result = max(result, math.sqrt(val))
425 return result
427s.rolling(window=3, min_periods=3).apply(max_sqrt)
428```
430**Limitation:** Rolling UDFs do NOT support null values.

Rolling Window UDFs

import math

s = cudf.Series([16, 25, 36, 49, 64, 81], dtype="float64")

def max_sqrt(window):
    result = 0
    for val in window:
        result = max(result, math.sqrt(val))
    return result

s.rolling(window=3, min_periods=3).apply(max_sqrt)

Limitation: Rolling UDFs do NOT support null values.

Raw lines 414 to 431
432### Custom Numba CUDA Kernels on cuDF Columns
434For maximum control, write CUDA kernels that operate directly on cuDF columns:
436```python
437from numba import cuda
439@cuda.jit
440def gpu_multiply(in_col, out_col, multiplier):
441 i = cuda.grid(1)
442 if i < in_col.size:
443 out_col[i] = in_col[i] * multiplier
445import cupy as cp
446# Reject/fill nulls according to the contract before obtaining a dense array.
447in_values = df["a"].to_cupy()
448out_values = cp.empty_like(in_values)
449gpu_multiply.forall(len(df))(in_values, out_values, 10.0)
450cuda.synchronize()
451df["result"] = cudf.Series(out_values, index=df.index)
452```

Custom Numba CUDA Kernels on cuDF Columns

For maximum control, write CUDA kernels that operate directly on cuDF columns:

from numba import cuda

@cuda.jit
def gpu_multiply(in_col, out_col, multiplier):
    i = cuda.grid(1)
    if i < in_col.size:
        out_col[i] = in_col[i] * multiplier

import cupy as cp
# Reject/fill nulls according to the contract before obtaining a dense array.
in_values = df["a"].to_cupy()
out_values = cp.empty_like(in_values)
gpu_multiply.forall(len(df))(in_values, out_values, 10.0)
cuda.synchronize()
df["result"] = cudf.Series(out_values, index=df.index)
Raw lines 432 to 453
454### UDF Limitations
456- Only numeric non-decimal types have full support; strings have partial support.
457- `**kwargs` not supported.
458- Bitwise operations not implemented in UDFs.
459- GroupBy JIT: no nulls, only int32/64 and float32/64, cannot return new columns.
460- Rolling UDFs: no null support.
462---

UDF Limitations

  • Only numeric non-decimal types have full support; strings have partial support.
  • **kwargs not supported.
  • Bitwise operations not implemented in UDFs.
  • GroupBy JIT: no nulls, only int32/64 and float32/64, cannot return new columns.
  • Rolling UDFs: no null support.

Raw lines 454 to 463
464## Missing Data Handling
466- Missing values are `<NA>` (not NaN) — cuDF uses a separate null mask, not NaN sentinels.
467- All dtypes are nullable (including integers — no float coercion for missing ints).
468- `np.nan` inserted into integer columns becomes `<NA>` without casting to float.
470```python
471s = cudf.Series([1, None, 3, None, 5])
473s.isna() # Boolean mask
474s.notna()
475s.fillna(0) # Fill with scalar
476df.fillna({"a": 0, "b": 1}) # DataFrame per-column filling
477s.dropna()
479# Aggregations skip NA by default
480s.sum() # skipna=True (default)
481s.sum(skipna=False) # Propagates NA
483# GroupBy excludes NA groups by default
484df.groupby("a", dropna=False).sum() # Include NA groups
485```
487---

Missing Data Handling

  • Missing values are <NA> (not NaN) — cuDF uses a separate null mask, not NaN sentinels.
  • All dtypes are nullable (including integers — no float coercion for missing ints).
  • np.nan inserted into integer columns becomes <NA> without casting to float.
s = cudf.Series([1, None, 3, None, 5])

s.isna()                # Boolean mask
s.notna()
s.fillna(0)             # Fill with scalar
df.fillna({"a": 0, "b": 1})  # DataFrame per-column filling
s.dropna()

# Aggregations skip NA by default
s.sum()                 # skipna=True (default)
s.sum(skipna=False)     # Propagates NA

# GroupBy excludes NA groups by default
df.groupby("a", dropna=False).sum()  # Include NA groups

Raw lines 464 to 488
489## Data Types
491| Category | Types |
492|----------|-------|
493| Integer | `int8`, `int16`, `int32`, `int64`, `uint32`, `uint64` |
494| Float | `float32`, `float64` |
495| Datetime | `datetime64[s/ms/us/ns]` |
496| Timedelta | `timedelta[s/ms/us/ns]` |
497| Categorical | `CategoricalDtype` |
498| String | `object` / `string` |
499| Decimal | `Decimal32Dtype`, `Decimal64Dtype`, `Decimal128Dtype` |
500| List | `ListDtype` (nested lists) |
501| Struct | `StructDtype` (dict-like) |
503All types are nullable. List columns have a `.list` accessor (`get()`, `len()`, `contains()`, `sort_values()`, `unique()`, `concat()`). Struct columns have a `.struct` accessor (`field()`, `explode()`).
505**No `object` dtype for arbitrary Python objects** — `object` dtype only stores strings.
507---

Data Types

CategoryTypes
Integerint8, int16, int32, int64, uint32, uint64
Floatfloat32, float64
Datetimedatetime64[s/ms/us/ns]
Timedeltatimedelta[s/ms/us/ns]
CategoricalCategoricalDtype
Stringobject / string
DecimalDecimal32Dtype, Decimal64Dtype, Decimal128Dtype
ListListDtype (nested lists)
StructStructDtype (dict-like)

All types are nullable. List columns have a .list accessor (get(), len(), contains(), sort_values(), unique(), concat()). Struct columns have a .struct accessor (field(), explode()).

**No object dtype for arbitrary Python objects** — object dtype only stores strings.


Raw lines 489 to 508
509## Memory Management

Memory Management

Raw lines 509 to 510
511### RMM (RAPIDS Memory Manager)
513cuDF uses RMM for GPU memory allocation. Configure it for your workload:
515```python
516import rmm
518# Pool allocator (recommended for production — avoids per-allocation cudaMalloc overhead)
519pool = rmm.mr.PoolMemoryResource(
520 rmm.mr.CudaMemoryResource(),
521 initial_pool_size="1GiB",
522 maximum_pool_size="4GiB"
524rmm.mr.set_current_device_resource(pool)
526# Managed memory (allows datasets larger than GPU memory)
527rmm.mr.set_current_device_resource(rmm.mr.ManagedMemoryResource())
529# Managed + pool (best of both)
530pool = rmm.mr.PoolMemoryResource(
531 rmm.mr.ManagedMemoryResource(),
532 initial_pool_size="1GiB"
534rmm.mr.set_current_device_resource(pool)
535```

RMM (RAPIDS Memory Manager)

cuDF uses RMM for GPU memory allocation. Configure it for your workload:

import rmm

# Pool allocator (recommended for production — avoids per-allocation cudaMalloc overhead)
pool = rmm.mr.PoolMemoryResource(
    rmm.mr.CudaMemoryResource(),
    initial_pool_size="1GiB",
    maximum_pool_size="4GiB"
)
rmm.mr.set_current_device_resource(pool)

# Managed memory (allows datasets larger than GPU memory)
rmm.mr.set_current_device_resource(rmm.mr.ManagedMemoryResource())

# Managed + pool (best of both)
pool = rmm.mr.PoolMemoryResource(
    rmm.mr.ManagedMemoryResource(),
    initial_pool_size="1GiB"
)
rmm.mr.set_current_device_resource(pool)
Raw lines 511 to 536
537### Aligning CuPy and Numba with RMM
539When using cuDF with CuPy or Numba, align all libraries on the same allocator to avoid memory fragmentation:
541```python
542# CuPy
543from rmm.allocators.cupy import rmm_cupy_allocator
544import cupy
545cupy.cuda.set_allocator(rmm_cupy_allocator)
547# Numba
548from rmm.allocators.numba import RMMNumbaManager
549from numba import cuda
550cuda.set_memory_manager(RMMNumbaManager)
551```

Aligning CuPy and Numba with RMM

When using cuDF with CuPy or Numba, align all libraries on the same allocator to avoid memory fragmentation:

# CuPy
from rmm.allocators.cupy import rmm_cupy_allocator
import cupy
cupy.cuda.set_allocator(rmm_cupy_allocator)

# Numba
from rmm.allocators.numba import RMMNumbaManager
from numba import cuda
cuda.set_memory_manager(RMMNumbaManager)
Raw lines 537 to 552
553### Copy-on-Write
555cuDF 26.08 enables copy-on-write by default for the pandas 3 transition. The old
556`copy_on_write` option and `CUDF_COPY_ON_WRITE` toggle are removed. Avoid mutating
557a borrowed array to update a DataFrame: compute an output and assign it as a column.
559Slices, `.head()`, shallow copies, and view-generating methods share memory until one is modified. Reduces memory usage significantly for workflows with many derived DataFrames.

Copy-on-Write

cuDF 26.08 enables copy-on-write by default for the pandas 3 transition. The old copy_on_write option and CUDF_COPY_ON_WRITE toggle are removed. Avoid mutating a borrowed array to update a DataFrame: compute an output and assign it as a column.

Slices, .head(), shallow copies, and view-generating methods share memory until one is modified. Reduces memory usage significantly for workflows with many derived DataFrames.

Raw lines 553 to 560
561### Memory Profiling
563```python
564import rmm.statistics
566rmm.statistics.enable_statistics()
567stats = rmm.statistics.get_statistics()
568# Returns: current_bytes, current_count, peak_bytes, peak_count, total_bytes, total_count
569```
571---

Memory Profiling

import rmm.statistics

rmm.statistics.enable_statistics()
stats = rmm.statistics.get_statistics()
# Returns: current_bytes, current_count, peak_bytes, peak_count, total_bytes, total_count

Raw lines 561 to 572
573## Interoperability

Interoperability

Raw lines 573 to 574
575### CuPy (Device-Resident Interchange)
577```python
578import cupy as cp
580# cuDF → CuPy
581arr = df.to_cupy() # May allocate/copy to combine columns and coerce dtype
582arr = cp.asarray(df["col"]) # Series → 1D CuPy array
583arr = df["col"].values # Series → 1D CuPy array
585# CuPy → cuDF
586df = cudf.DataFrame(cupy_2d_array)
587s = cudf.Series(cupy_1d_array)
589# Via DLPack
590df = cudf.from_dlpack(cupy_array.__dlpack__())
591```

CuPy (Device-Resident Interchange)

import cupy as cp

# cuDF → CuPy
arr = df.to_cupy()             # May allocate/copy to combine columns and coerce dtype
arr = cp.asarray(df["col"])    # Series → 1D CuPy array
arr = df["col"].values         # Series → 1D CuPy array

# CuPy → cuDF
df = cudf.DataFrame(cupy_2d_array)
s = cudf.Series(cupy_1d_array)

# Via DLPack
df = cudf.from_dlpack(cupy_array.__dlpack__())
Raw lines 575 to 592
593### Arrow (Host Transfer)
595```python
596arrow_table = df.to_arrow()
597df = cudf.DataFrame.from_arrow(arrow_table)
598```

Arrow (Host Transfer)

arrow_table = df.to_arrow()
df = cudf.DataFrame.from_arrow(arrow_table)
Raw lines 593 to 599
600### RAPIDS Ecosystem
602- **cuML:** Accepts cuDF DataFrames directly for ML pipelines.
603- **cuGraph:** Accepts cuDF DataFrames for graph analytics.
604- **Dask-cuDF:** Distributed GPU DataFrames (see below).

RAPIDS Ecosystem

  • cuML: Accepts cuDF DataFrames directly for ML pipelines.
  • cuGraph: Accepts cuDF DataFrames for graph analytics.
  • Dask-cuDF: Distributed GPU DataFrames (see below).
Raw lines 600 to 605
606### CUDA Array Interface
608Non-null numeric Series can expose `__cuda_array_interface__`; dtype, null masks, copy-on-write and ownership constrain interoperability. A DataFrame is columnar, so producing one dense 2D array generally copies.
610---

CUDA Array Interface

Non-null numeric Series can expose __cuda_array_interface__; dtype, null masks, copy-on-write and ownership constrain interoperability. A DataFrame is columnar, so producing one dense 2D array generally copies.


Raw lines 606 to 611
612## Multi-GPU with Dask-cuDF
614For datasets larger than a single GPU's memory, or for multi-GPU parallelism:
616```python
617import dask_cudf
618from dask.distributed import Client
619from dask_cuda import LocalCUDACluster
621# One worker per GPU
622cluster = LocalCUDACluster()
623client = Client(cluster)
625# From files
626ddf = dask_cudf.read_csv("path/*.csv")
627ddf = dask_cudf.read_parquet("path/")
629# From cuDF DataFrame
630ddf = dask_cudf.from_cudf(df, npartitions=16)
632# Operations (lazy — call .compute() to execute)
633result = ddf.groupby("a").sum().compute()
635# Persist in GPU memory for repeated access
636ddf = ddf.persist()
637```
639Key differences from cuDF: `.iloc` not supported, must call `.compute()` to materialize, transpose not implemented.
641---

Multi-GPU with Dask-cuDF

For datasets larger than a single GPU's memory, or for multi-GPU parallelism:

import dask_cudf
from dask.distributed import Client
from dask_cuda import LocalCUDACluster

# One worker per GPU
cluster = LocalCUDACluster()
client = Client(cluster)

# From files
ddf = dask_cudf.read_csv("path/*.csv")
ddf = dask_cudf.read_parquet("path/")

# From cuDF DataFrame
ddf = dask_cudf.from_cudf(df, npartitions=16)

# Operations (lazy — call .compute() to execute)
result = ddf.groupby("a").sum().compute()

# Persist in GPU memory for repeated access
ddf = ddf.persist()

Key differences from cuDF: .iloc not supported, must call .compute() to materialize, transpose not implemented.


Raw lines 612 to 642
643## Performance Optimization
6451. **Start with cudf.pandas** for easiest adoption — zero code changes, automatic GPU/CPU fallback.
6472. **Switch to direct cuDF API for max performance** — avoids proxy overhead and fallback copying costs.
6493. **Prefer Parquet over CSV** — columnar format, faster GPU reads, predicate pushdown, better compression.
6514. **Use pool allocators** via RMM — avoids per-allocation `cudaMalloc` overhead.
6535. **Account for copy-on-write**, enabled in 26.08: mutation can materialize shared buffers.
6556. **Reshape data to be long** (more rows, fewer columns) — GPUs parallelize over rows.
6577. **Never iterate** — use vectorized operations exclusively. `for row in df.iterrows()` defeats the purpose of GPU acceleration.
6598. **Benchmark actual shapes.** No fixed row-count threshold proves a GPU benefit.
6619. **Use vectorized string ops** (`.str.` accessor) instead of row-wise string UDFs.
66310. **Use CuPy for row-wise math** that cuDF doesn't support natively.
66511. **Use Numba CUDA kernels** for complex element-wise operations.
66712. **Align all RAPIDS libraries on the same RMM allocator** to avoid memory fragmentation.
66913. **For distributed workloads**, use Dask-cuDF with `persist()` to keep data on GPU memory.
671---

Performance Optimization

  1. Start with cudf.pandas for easiest adoption — zero code changes, automatic GPU/CPU fallback.
  1. Switch to direct cuDF API for max performance — avoids proxy overhead and fallback copying costs.
  1. Prefer Parquet over CSV — columnar format, faster GPU reads, predicate pushdown, better compression.
  1. Use pool allocators via RMM — avoids per-allocation cudaMalloc overhead.
  1. Account for copy-on-write, enabled in 26.08: mutation can materialize shared buffers.
  1. Reshape data to be long (more rows, fewer columns) — GPUs parallelize over rows.
  1. Never iterate — use vectorized operations exclusively. for row in df.iterrows() defeats the purpose of GPU acceleration.
  1. Benchmark actual shapes. No fixed row-count threshold proves a GPU benefit.
  1. Use vectorized string ops (.str. accessor) instead of row-wise string UDFs.
  1. Use CuPy for row-wise math that cuDF doesn't support natively.
  1. Use Numba CUDA kernels for complex element-wise operations.
  1. Align all RAPIDS libraries on the same RMM allocator to avoid memory fragmentation.
  1. For distributed workloads, use Dask-cuDF with persist() to keep data on GPU memory.

Raw lines 643 to 672
673## Key Differences from pandas
6751. **Result ordering is non-deterministic** by default (groupby, joins, etc.). Use `sort=True` or `cudf.set_option("mode.pandas_compatible", True)`.
6772. **All types are nullable.** Missing values are `<NA>`, not NaN. Integer columns with missing values stay integer (no float coercion).
6793. **No iteration.** `for val in series` is not supported. Convert to pandas first if you must iterate.
6814. **Unique column names required.** No duplicate column names.
6835. **No arbitrary Python objects.** The `object` dtype only stores strings.
6856. **`.apply()` uses Numba JIT.** Only a subset of Python is supported inside UDFs — no arbitrary Python objects, no external library calls.
6877. **Floating-point results may differ slightly** due to GPU parallel operation ordering. Use tolerance-based comparisons.
6898. **GroupBy defaults to `sort=False`** (pandas defaults to `sort=True`).
6919. **No ExtensionDtype support** from pandas.
693---

Key Differences from pandas

  1. Result ordering is non-deterministic by default (groupby, joins, etc.). Use sort=True or cudf.set_option("mode.pandas_compatible", True).
  1. All types are nullable. Missing values are <NA>, not NaN. Integer columns with missing values stay integer (no float coercion).
  1. No iteration. for val in series is not supported. Convert to pandas first if you must iterate.
  1. Unique column names required. No duplicate column names.
  1. No arbitrary Python objects. The object dtype only stores strings.
  1. **.apply() uses Numba JIT.** Only a subset of Python is supported inside UDFs — no arbitrary Python objects, no external library calls.
  1. Floating-point results may differ slightly due to GPU parallel operation ordering. Use tolerance-based comparisons.
  1. **GroupBy defaults to sort=False** (pandas defaults to sort=True).
  1. No ExtensionDtype support from pandas.

Raw lines 673 to 694
695## Common Migration Patterns

Common Migration Patterns

Raw lines 695 to 696
697### Pattern 1: Zero-Effort (cudf.pandas)
698```python
699%load_ext cudf.pandas
700import pandas as pd
701# Everything else stays exactly the same
702```

Pattern 1: Zero-Effort (cudf.pandas)

%load_ext cudf.pandas
import pandas as pd
# Everything else stays exactly the same
Raw lines 697 to 703
704### Pattern 2: Direct Import Swap
705```python
706# Before
707import pandas as pd
708df = pd.read_csv("data.csv")
709result = df.groupby("col").mean()
711# After
712import cudf
713df = cudf.read_csv("data.csv")
714result = df.groupby("col").mean()
715```

Pattern 2: Direct Import Swap

# Before
import pandas as pd
df = pd.read_csv("data.csv")
result = df.groupby("col").mean()

# After
import cudf
df = cudf.read_csv("data.csv")
result = df.groupby("col").mean()
Raw lines 704 to 716
717### Pattern 3: Replace Iteration with Vectorized Ops
718```python
719# Before (pandas — slow even on CPU)
720for idx, row in df.iterrows():
721 df.at[idx, "c"] = row["a"] + row["b"]
723# After (cuDF)
724df["c"] = df["a"] + df["b"]
725```

Pattern 3: Replace Iteration with Vectorized Ops

# Before (pandas — slow even on CPU)
for idx, row in df.iterrows():
    df.at[idx, "c"] = row["a"] + row["b"]

# After (cuDF)
df["c"] = df["a"] + df["b"]
Raw lines 717 to 726
727### Pattern 4: Replace apply() with Vectorized
728```python
729# Before
730df["result"] = df.apply(lambda row: row["a"] ** 2 + row["b"], axis=1)
732# After (vectorized — much faster)
733df["result"] = df["a"] ** 2 + df["b"]
734```

Pattern 4: Replace apply() with Vectorized

# Before
df["result"] = df.apply(lambda row: row["a"] ** 2 + row["b"], axis=1)

# After (vectorized — much faster)
df["result"] = df["a"] ** 2 + df["b"]
Raw lines 727 to 735
736### Pattern 5: GPU Processing, CPU at Boundaries
737```python
738# Load and process on GPU
739gdf = cudf.read_parquet("data.parquet")
740result = gdf.groupby("key").agg({"val": "sum"})
742# Convert to pandas only when needed (plotting, export, etc.)
743pdf = result.to_pandas()
744pdf.plot()
745```

Pattern 5: GPU Processing, CPU at Boundaries

# Load and process on GPU
gdf = cudf.read_parquet("data.parquet")
result = gdf.groupby("key").agg({"val": "sum"})

# Convert to pandas only when needed (plotting, export, etc.)
pdf = result.to_pandas()
pdf.plot()
Raw lines 736 to 746
747### Pattern 6: CuPy for Unsupported Math
748```python
749import cupy as cp
751# Convert to CuPy for operations cuDF doesn't support
752arr = df[["x", "y", "z"]].to_cupy()
753norms = cp.linalg.norm(arr, axis=1)
754df["norm"] = cudf.Series(norms, index=df.index)
755```
757---

Pattern 6: CuPy for Unsupported Math

import cupy as cp

# Convert to CuPy for operations cuDF doesn't support
arr = df[["x", "y", "z"]].to_cupy()
norms = cp.linalg.norm(arr, axis=1)
df["norm"] = cudf.Series(norms, index=df.index)

Raw lines 747 to 758
759## Configuration
761```python
762cudf.set_option("mode.pandas_compatible", True) # Match pandas behavior
763cudf.describe_option() # List all options
764```
766| Environment Variable | Purpose |
767|---------------------|---------|
768| `CUDF_PANDAS_RMM_MODE` | Control memory allocator for cudf.pandas |
769| `CUDF_PANDAS_FALLBACK_MODE=1` | Force CPU-only execution in cudf.pandas |

Configuration

cudf.set_option("mode.pandas_compatible", True)    # Match pandas behavior
cudf.describe_option()                             # List all options
Environment VariablePurpose
CUDF_PANDAS_RMM_MODEControl memory allocator for cudf.pandas
CUDF_PANDAS_FALLBACK_MODE=1Force CPU-only execution in cudf.pandas
Raw lines 759 to 769