accelerated-computing-cudf
Official NVIDIA-authored guidance for NVIDIA cuDF GPU DataFrames, pandas acceleration, dask-cuDF, ETL, joins, groupby, CSV/Parquet I/O, nullable semantics, and multi-GPU DataFrame workloads.
Install
npx skills add https://github.com/NVIDIA/skills/tree/main/skills/accelerated-computing-cudf
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install nvidia-skills@llmmart
git clone https://github.com/NVIDIA/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole nvidia/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
cuDF & dask-cuDF Implementer's Guide
Compatibility
- Release tracked by this skill: 26.04.
- Requires NVIDIA Volta or newer on CUDA 12, or Turing or newer on CUDA 13. Release 26.04 supports CUDA 12.2-12.9 with driver 535+ or CUDA 13.0-13.1 with driver 580+, and Python 3.11-3.14. cuDF sweet spot: >100K rows.
Naming
Use NVIDIA library-first wording in user-facing answers. Keep literal RAPIDS/rapidsai URLs, package names, and release metadata when citing sources.
Role
You are a cuDF expert helping an implementer work with GPU DataFrames. The user understands pandas and their data — your job is to get them to correct, fast GPU code with minimal friction. Choose the path from the user's intent: cudf.pandas for broad compatibility or minimal-change acceleration, explicit cuDF for named DataFrame migrations, hot ETL paths, and parity-sensitive work. Treat source schema, row counts, null placement, ordering, and numeric tolerances as user-visible behavior.
Critical Rules
- Choose the right cuDF path. Use
cudf.pandasfor broad compatibility or minimal-change acceleration. Use explicit cuDF when the user asks to migrate DataFrame code, inspect parity, optimize a visible ETL hot path, or control unsupported operations. - Size gate: 100K rows minimum. Below that, GPU transfer overhead usually beats the speedup; use small data for correctness and benchmark larger working sets for performance.
- Keep conversions at boundaries. Use
.to_pandas(),.values, or.numpy()for display, plotting, CPU-only libraries, or final output boundaries. Keep intermediate ETL data on GPU. - Float32 is your friend. cuDF operations on float64 are slower; cast early when precision allows.
- Validate semantics on representative slices. For null handling, joins, time series, reshape, or grouped logic, keep a small pandas reference path and compare shape, labels, null counts, ordering, and representative values before claiming parity.
- For data > GPU memory, move to dask-cuDF with
enable_cudf_spill=True. Seereferences/dask-cudf-patterns.md.
Three Paths to GPU DataFrames
Path 1: cudf.pandas Accelerator (Compatibility / Minimal Change)
Use when the user needs a small code change, third-party pandas compatibility, or one code path that can keep running while unsupported operations fall back.
Jupyter/IPython:
%load_ext cudf.pandas
import pandas as pd # now GPU-backed; falls back silently for unsupported ops
Script:
python -m cudf.pandas my_script.py
With multiprocessing:
import cudf.pandas
cudf.pandas.install() # must come BEFORE pandas import, before Pool creation
from multiprocessing import Pool
Confirm acceleration with the cudf.pandas profiler before claiming speedup.
For notebook, CLI, and stats examples, read
references/cudf-pandas-accelerator.md. If the profile shows the hot path
running on CPU, use Path 2 for explicit cuDF control.
Path 2: Explicit cuDF API
For full control, hot-path optimization, named DataFrame migrations, and parity-sensitive operations:
import cudf
# Read data directly to GPU
df = cudf.read_parquet("data.parquet")
# Operations mirror pandas
result = df.groupby("key")["value"].sum()
merged = df.merge(lookup, on="id", how="left")
filtered = df[df["amount"] > 1000]
# String operations
df["clean"] = df["name"].str.strip().str.lower()
# To check API coverage before committing to migration:
# See references/api-patterns.md for known gaps and workarounds
Keep data on GPU end-to-end. Only call .to_pandas() at the very end for display or CPU or non-GPU handoff.
Prefer explicit cuDF for tasks involving read_csv/read_parquet, joins,
groupby, reshape, nullable types, fillna/where, time buckets, rolling
windows, or CPU/GPU parity checks. Add a small CPU/GPU validation path when
semantics matter instead of relying on successful execution alone.
For pandas code with null handling, reshape, or time-series behavior, read
references/api-patterns.md for the relevant semantic checklist before
rewriting. A cudf.pandas bootstrap is enough for a minimal-change request; an
implementation request should make the hot path explicit and observable.
For reshape-heavy pandas code (pivot_table, melt, stack/unstack,
crosstab), keep the source schema as part of the contract: index labels,
column labels or levels, fill_value, aggfunc, margins, and normalization.
Use explicit cuDF where the equivalent is supported; use cudf.pandas or a
narrow compatibility boundary when exact pandas reshape semantics matter more
than rewriting every operation. Add a small pandas-reference parity check for
shape, labels, and representative values before finalizing. See
references/api-patterns.md.
Path 3: dask-cuDF (Multi-GPU / Large Data)
When dataset exceeds GPU memory. See references/dask-cudf-patterns.md for full patterns.
from dask_cuda import LocalCUDACluster
from dask.distributed import Client
import dask_cudf
cluster = LocalCUDACluster(enable_cudf_spill=True) # one worker per GPU
client = Client(cluster)
ddf = dask_cudf.read_parquet("s3://bucket/data/*.parquet")
result = ddf.groupby("key").agg({"value": "sum"}).compute()
Memory Management
Enable spill before OOM happens (not after):
import cudf
cudf.set_option("spill", True) # spill to host RAM when GPU is full
RMM pool allocator (reduces cudaMalloc overhead in pipelines with many allocations):
import rmm
rmm.set_current_device_resource(rmm.mr.CudaAsyncMemoryResource())
# Must be called BEFORE any cuDF operations
| GPU Free vs Dataset | Strategy |
|---|---|
| Free > 2× dataset | Single GPU cuDF |
| Free 1–2× dataset | cuDF + cudf.set_option("spill", True) |
| Dataset > GPU mem | dask-cuDF |
| Dataset > node mem | dask-cuDF + multi-node (see accelerated-computing-mpf) |
Troubleshooting
No speedup vs pandas:
- Data < 100K rows? GPU overhead dominates, so treat the run as correctness validation and measure speedup on a larger working set.
- Run
%%cudf.pandas.profile— high CPU % means many fallbacks. Identify and fix those ops. - Check
references/api-patterns.mdfor known gaps.
OOM (CUDA out of memory):
- Enable spill:
cudf.set_option("spill", True) - If allocator fragmentation or repeated allocation overhead is visible, use the
accelerated-computing-rmmmemory-resource setup guidance before GPU allocations - Still failing: move to dask-cuDF
AttributeError / NotImplementedError:
- Check
references/api-patterns.mdfor the specific operation - Keep that one operation on CPU at a narrow boundary and continue the supported pipeline on GPU
- Use
.to_pandas()only for the unsupported op, then.from_pandas()back
Wrong results vs pandas:
- Null/NaN handling differs: cuDF uses
<NA>(nullable) by default, pandas usesNaN. Seereferences/api-patterns.md. - Sort stability: cuDF sort is not guaranteed stable unless
stable=Trueis passed - If the difference is due to floating point differences, try casting to higher precision floats (e.g.
float64instead offloat32). If the results are still different, stop. GPU and CPU algorithms will always produce different results on floating point numbers due to the non-associativity of floating point arithmetic and that cannot be fixed.
Nullable and Fill Semantics
When the user explicitly cares about pandas nullable dtypes, fillna,
where/mask, or grouped null behavior, treat parity checks as part of the
implementation. See references/api-patterns.md for nullable dtype examples.
- Preserve nullable integer/string columns instead of filling them with sentinel values unless the source code already did that.
- Keep
where/masksemantics when they encode a condition. Use broadfillnaonly when the condition is exactly null-only. - Compare with
to_pandas(nullable=True)when the pandas reference uses nullable extension dtypes. - Put the parity check in a reusable helper next to the GPU path, so future changes exercise the same nullable conversion and aggregation checks.
- Validate row counts, null counts, mask truth tables, grouped aggregates, and representative dtypes before claiming semantic parity.
Reference Files
references/cudf-pandas-accelerator.md— Profiling, fallback detection, cudf.pandas deep divereferences/api-patterns.md— Known API gaps, workarounds, semantic differencesreferences/dask-cudf-patterns.md— Multi-GPU patterns, best practices, partition tuning
External Documentation
Use WebFetch to retrieve detailed API signatures, parameter descriptions, and examples on demand.
- cuDF Documentation: https://docs.nvidia.com/cudf/
- dask-cuDF API Reference: https://docs.nvidia.com/dask-cudf/
- GitHub: https://github.com/NVIDIA/cudf
- CHANGELOG: https://github.com/NVIDIA/cudf/blob/main/CHANGELOG.md
Files (skills)
-
evals
-
files
-
cudf-apply-udf
-
code
-
generate_data.py 1.5 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Generate synthetic insurance claims data for UDF processing.""" import os import numpy as np import pandas as pd SEED = 42 N_ROWS = 40_000 def generate(): if os.path.exists("claims.csv"): return rng = np.random.default_rng(SEED) policy_types = ["auto", "home", "health", "life", "travel"] risk_levels = ["low", "medium", "high"] regions = ["northeast", "southeast", "midwest", "west", "pacific"] df = pd.DataFrame({ "claim_id": range(N_ROWS), "policy_type": rng.choice(policy_types, N_ROWS), "risk_level": rng.choice(risk_levels, N_ROWS, p=[0.5, 0.35, 0.15]), "region": rng.choice(regions, N_ROWS), "age": rng.integers(18, 85, N_ROWS), "claim_amount": np.round(rng.exponential(5000, N_ROWS), 2), "deductible": np.round(rng.choice([250, 500, 1000, 2000, 5000], N_ROWS).astype(float), 2), "premium_monthly": np.round(rng.uniform(50, 800, N_ROWS), 2), "years_as_customer": rng.integers(0, 30, N_ROWS), "num_prior_claims": rng.integers(0, 10, N_ROWS), "credit_score": rng.integers(300, 850, N_ROWS), "property_value": np.round(rng.uniform(50_000, 1_000_000, N_ROWS), 2), }) df.to_csv("claims.csv", index=False) print(f"Generated {len(df)} insurance claims -> claims.csv") if __name__ == "__main__": generate() -
udf_pipeline.py 5.3 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """UDF-heavy processing pipeline on insurance claims data. Uses apply(), applymap(), and custom functions for row-wise and element-wise transformations on a pandas DataFrame. """ import numpy as np import pandas as pd from generate_data import generate def load_data(): generate() df = pd.read_csv("claims.csv") print(f"Loaded {len(df)} claims") return df # --- Row-wise UDFs used with apply(axis=1) --- def calculate_risk_score(row): """Complex row-wise risk scoring function.""" base_score = 50 # Age factor if row["age"] < 25: base_score += 15 elif row["age"] > 65: base_score += 10 else: base_score -= 5 # Claims history base_score += row["num_prior_claims"] * 8 # Credit score factor if row["credit_score"] >= 750: base_score -= 20 elif row["credit_score"] >= 650: base_score -= 10 elif row["credit_score"] < 550: base_score += 15 # Risk level multiplier if row["risk_level"] == "high": base_score *= 1.5 elif row["risk_level"] == "medium": base_score *= 1.2 # Loyalty discount if row["years_as_customer"] > 10: base_score *= 0.85 elif row["years_as_customer"] > 5: base_score *= 0.92 return round(base_score, 2) def calculate_payout(row): """Calculate adjusted payout amount based on multiple conditions.""" amount = row["claim_amount"] deductible = row["deductible"] net = max(0, amount - deductible) # Cap by policy type caps = {"auto": 50_000, "home": 200_000, "health": 100_000, "life": 500_000, "travel": 10_000} cap = caps.get(row["policy_type"], 50_000) net = min(net, cap) # Loyalty bonus: extra 5% for long-term customers if row["years_as_customer"] > 15: net *= 1.05 # High-risk penalty: reduce by 10% if row["risk_level"] == "high" and row["num_prior_claims"] > 5: net *= 0.90 return round(net, 2) def classify_claim_tier(row): """Classify claim into processing tier based on multiple factors.""" amount = row["claim_amount"] risk = row["risk_level"] priors = row["num_prior_claims"] if amount > 20_000 or (risk == "high" and priors > 3): return "tier_3_manual" elif amount > 5_000 or (risk == "medium" and priors > 2): return "tier_2_review" else: return "tier_1_auto" # --- Column-wise UDFs --- def normalize_score(series): """Min-max normalize a numeric series.""" return (series - series.min()) / (series.max() - series.min()) def winsorize(series, lower=0.05, upper=0.95): """Clip values at the given percentiles.""" lo = series.quantile(lower) hi = series.quantile(upper) return series.clip(lo, hi) # --- Element-wise UDF --- def format_currency(val): """Format a numeric value as currency string.""" if pd.isna(val): return "$0.00" return f"${val:,.2f}" def credit_bucket(val): """Bucket a credit score into a category.""" if val >= 750: return "excellent" elif val >= 700: return "good" elif val >= 650: return "fair" elif val >= 550: return "poor" else: return "very_poor" def process_claims(df): """Apply all UDFs to the claims DataFrame.""" # Row-wise apply (the expensive operations) print("Computing risk scores (row-wise apply)...") df["risk_score"] = df.apply(calculate_risk_score, axis=1) print("Computing payouts (row-wise apply)...") df["payout"] = df.apply(calculate_payout, axis=1) print("Classifying claims (row-wise apply)...") df["claim_tier"] = df.apply(classify_claim_tier, axis=1) # Column-wise UDFs print("Normalizing and winsorizing...") df["risk_score_norm"] = normalize_score(df["risk_score"]) df["claim_amount_winsorized"] = winsorize(df["claim_amount"]) df["premium_norm"] = normalize_score(df["premium_monthly"]) # Element-wise apply (applymap-style via apply on columns) print("Formatting and bucketing...") df["credit_bucket"] = df["credit_score"].apply(credit_bucket) df["payout_formatted"] = df["payout"].apply(format_currency) # Element-wise on multiple numeric columns numeric_cols = ["claim_amount", "deductible", "premium_monthly", "property_value"] formatted = df[numeric_cols].applymap(format_currency) for col in numeric_cols: df[f"{col}_fmt"] = formatted[col] return df def summarize(df): """Summarize processed claims.""" print(f"\nProcessed {len(df)} claims") print(f"Risk score stats: mean={df['risk_score'].mean():.1f}, " f"std={df['risk_score'].std():.1f}") print(f"Total payouts: ${df['payout'].sum():,.2f}") tier_counts = df["claim_tier"].value_counts() print(f"\nClaim tiers:\n{tier_counts}") credit_dist = df["credit_bucket"].value_counts() print(f"\nCredit distribution:\n{credit_dist}") by_type = df.groupby("policy_type").agg( avg_risk=("risk_score", "mean"), total_payout=("payout", "sum"), claim_count=("claim_id", "count"), ).round(2) print(f"\nBy policy type:\n{by_type}") def main(): df = load_data() df = process_claims(df) summarize(df) if __name__ == "__main__": main()
-
-
-
cudf-csv-etl
-
code
-
etl_pipeline.py 2.6 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """CSV ETL pipeline: read, filter, compute, groupby, write parquet. Reads sales.csv, filters to completed orders, adds computed columns (revenue, discounted_revenue, age_group), runs a groupby aggregation by region and product, and writes the summary to parquet. """ import numpy as np import pandas as pd from generate_data import generate def load_data(): generate() df = pd.read_csv("sales.csv") print(f"Loaded {len(df)} rows from sales.csv") return df def filter_completed(df): """Keep only completed orders with quantity >= 2.""" mask = (df["status"] == "completed") & (df["quantity"] >= 2) filtered = df[mask].copy() print(f"Filtered to {len(filtered)} completed orders") return filtered def add_computed_columns(df): """Add revenue, discounted revenue, and age group columns.""" df["revenue"] = df["quantity"] * df["unit_price"] df["discounted_revenue"] = df["revenue"] * (1 - df["discount_pct"]) bins = [0, 25, 35, 50, 65, 100] labels = ["18-25", "26-35", "36-50", "51-65", "65+"] df["age_group"] = pd.cut(df["customer_age"], bins=bins, labels=labels) df["high_value"] = (df["discounted_revenue"] > 500).astype(int) print(f"Added computed columns; {df['high_value'].sum()} high-value orders") return df def aggregate_by_region_product(df): """Groupby region + product, compute summary statistics.""" summary = ( df.groupby(["region", "product"]) .agg( total_revenue=("revenue", "sum"), total_discounted=("discounted_revenue", "sum"), order_count=("order_id", "count"), avg_quantity=("quantity", "mean"), avg_unit_price=("unit_price", "mean"), high_value_count=("high_value", "sum"), ) .reset_index() ) summary["avg_discount_impact"] = ( 1 - summary["total_discounted"] / summary["total_revenue"] ) summary = summary.sort_values("total_revenue", ascending=False) print(f"Aggregated into {len(summary)} region-product groups") return summary def write_output(summary): """Write the summary to a parquet file.""" summary.to_parquet("sales_summary.parquet", index=False) print("Wrote sales_summary.parquet") def main(): df = load_data() df = filter_completed(df) df = add_computed_columns(df) summary = aggregate_by_region_product(df) write_output(summary) print("\nTop 5 region-product combos by revenue:") print(summary.head(5).to_string(index=False)) if __name__ == "__main__": main() -
generate_data.py 1.2 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Generate a synthetic sales CSV for the ETL pipeline.""" import os import numpy as np import pandas as pd SEED = 42 N_ROWS = 50_000 def generate(): if os.path.exists("sales.csv"): return rng = np.random.default_rng(SEED) regions = ["North", "South", "East", "West"] products = ["Widget", "Gadget", "Doohickey", "Thingamajig", "Whatchamacallit"] statuses = ["completed", "pending", "returned", "cancelled"] df = pd.DataFrame({ "order_id": range(N_ROWS), "region": rng.choice(regions, N_ROWS), "product": rng.choice(products, N_ROWS), "quantity": rng.integers(1, 50, N_ROWS), "unit_price": np.round(rng.uniform(5.0, 500.0, N_ROWS), 2), "discount_pct": np.round(rng.uniform(0.0, 0.3, N_ROWS), 3), "status": rng.choice(statuses, N_ROWS, p=[0.7, 0.1, 0.1, 0.1]), "customer_age": rng.integers(18, 80, N_ROWS), }) df.to_csv("sales.csv", index=False) print(f"Generated {len(df)} rows -> sales.csv") if __name__ == "__main__": generate()
-
-
-
cudf-groupby-agg
-
code
-
generate_data.py 1.4 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Generate synthetic employee performance data.""" import os import numpy as np import pandas as pd SEED = 42 N_EMPLOYEES = 50_000 def generate(): if os.path.exists("employees.csv"): return rng = np.random.default_rng(SEED) departments = ["Engineering", "Sales", "Marketing", "Finance", "HR", "Operations"] levels = ["Junior", "Mid", "Senior", "Lead", "Principal"] offices = ["NYC", "SF", "London", "Berlin", "Tokyo", "Sydney"] df = pd.DataFrame({ "employee_id": range(N_EMPLOYEES), "department": rng.choice(departments, N_EMPLOYEES), "level": rng.choice(levels, N_EMPLOYEES, p=[0.3, 0.3, 0.2, 0.12, 0.08]), "office": rng.choice(offices, N_EMPLOYEES), "salary": np.round(rng.normal(85_000, 25_000, N_EMPLOYEES).clip(30_000, 300_000), 2), "bonus": np.round(rng.exponential(5_000, N_EMPLOYEES), 2), "performance_score": np.round(rng.normal(3.5, 0.8, N_EMPLOYEES).clip(1.0, 5.0), 2), "years_tenure": rng.integers(0, 25, N_EMPLOYEES), "projects_completed": rng.integers(0, 50, N_EMPLOYEES), "training_hours": np.round(rng.exponential(20, N_EMPLOYEES), 1), }) df.to_csv("employees.csv", index=False) print(f"Generated {len(df)} employee records -> employees.csv") if __name__ == "__main__": generate() -
groupby_analysis.py 4.3 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Complex groupby aggregation and transform pipeline. Performs department-level, multi-key groupby, named aggregation, and transform-based feature engineering on employee data. """ import numpy as np import pandas as pd from generate_data import generate def load_data(): generate() df = pd.read_csv("employees.csv") print(f"Loaded {len(df)} employees") return df def department_summary(df): """Basic department-level aggregation with multiple functions.""" dept = df.groupby("department").agg( headcount=("employee_id", "count"), avg_salary=("salary", "mean"), median_salary=("salary", "median"), std_salary=("salary", "std"), total_bonus=("bonus", "sum"), avg_perf=("performance_score", "mean"), unique_levels=("level", "nunique"), unique_offices=("office", "nunique"), avg_tenure=("years_tenure", "mean"), total_projects=("projects_completed", "sum"), ).reset_index() dept = dept.sort_values("avg_salary", ascending=False) print(f"Department summary: {len(dept)} departments") return dept def multi_key_aggregation(df): """Groupby on department + level with named aggregation.""" result = df.groupby(["department", "level"]).agg( count=("employee_id", "count"), salary_mean=("salary", "mean"), salary_min=("salary", "min"), salary_max=("salary", "max"), salary_sum=("salary", "sum"), bonus_mean=("bonus", "mean"), perf_mean=("performance_score", "mean"), perf_std=("performance_score", "std"), tenure_mean=("years_tenure", "mean"), projects_sum=("projects_completed", "sum"), ).reset_index() result["salary_range"] = result["salary_max"] - result["salary_min"] print(f"Multi-key aggregation: {len(result)} groups") return result def office_department_crosstab(df): """Three-key groupby: department + office + level.""" cross = df.groupby(["department", "office", "level"]).agg( headcount=("employee_id", "count"), avg_salary=("salary", "mean"), total_training=("training_hours", "sum"), ).reset_index() print(f"Cross-tab: {len(cross)} groups") return cross def add_transform_features(df): """Use groupby transform to add group-relative features.""" # Department-level transforms df["dept_avg_salary"] = df.groupby("department")["salary"].transform("mean") df["dept_std_salary"] = df.groupby("department")["salary"].transform("std") df["salary_zscore"] = (df["salary"] - df["dept_avg_salary"]) / df["dept_std_salary"] # Level-level transforms df["level_avg_perf"] = df.groupby("level")["performance_score"].transform("mean") df["perf_vs_level"] = df["performance_score"] - df["level_avg_perf"] # Department rank by salary df["dept_salary_rank"] = df.groupby("department")["salary"].rank( method="dense", ascending=False ) # Department + level cumulative count df["dept_level_count"] = df.groupby(["department", "level"]).cumcount() + 1 # Percent of department total df["dept_salary_total"] = df.groupby("department")["salary"].transform("sum") df["salary_pct_of_dept"] = df["salary"] / df["dept_salary_total"] outlier_count = (df["salary_zscore"].abs() > 2).sum() print(f"Transform features added; {outlier_count} salary outliers (|z| > 2)") return df def top_performers_per_dept(df): """Get top 5 performers per department using groupby + nlargest.""" top = ( df.groupby("department") .apply(lambda g: g.nlargest(5, "performance_score")) .reset_index(drop=True) ) print(f"Top performers: {len(top)} rows") return top def main(): df = load_data() dept_summary = department_summary(df) multi_key = multi_key_aggregation(df) cross = office_department_crosstab(df) df_with_transforms = add_transform_features(df) top_perf = top_performers_per_dept(df) print(f"\nDepartment summary:\n{dept_summary.to_string(index=False)}") print(f"\nSample transformed rows:\n" f"{df_with_transforms[['department', 'level', 'salary', 'salary_zscore', 'perf_vs_level', 'dept_salary_rank']].head(10).to_string(index=False)}") if __name__ == "__main__": main()
-
-
-
cudf-multi-join
-
code
-
generate_data.py 2 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Generate three related CSVs: orders, customers, products.""" import os import numpy as np import pandas as pd SEED = 42 N_CUSTOMERS = 3_000 N_PRODUCTS = 200 N_ORDERS = 80_000 def generate(): if os.path.exists("orders.csv"): return rng = np.random.default_rng(SEED) # --- customers --- tiers = ["bronze", "silver", "gold", "platinum"] customers = pd.DataFrame({ "customer_id": range(N_CUSTOMERS), "customer_name": [f"Cust_{i:05d}" for i in range(N_CUSTOMERS)], "tier": rng.choice(tiers, N_CUSTOMERS, p=[0.4, 0.3, 0.2, 0.1]), "country": rng.choice(["US", "UK", "DE", "JP", "BR", "IN"], N_CUSTOMERS), "credit_limit": np.round(rng.uniform(500, 50_000, N_CUSTOMERS), 2), }) # --- products --- categories = ["electronics", "clothing", "food", "tools", "toys"] products = pd.DataFrame({ "product_id": range(N_PRODUCTS), "product_name": [f"Prod_{i:04d}" for i in range(N_PRODUCTS)], "category": rng.choice(categories, N_PRODUCTS), "base_price": np.round(rng.uniform(2.0, 800.0, N_PRODUCTS), 2), "weight_kg": np.round(rng.uniform(0.1, 30.0, N_PRODUCTS), 2), }) # --- orders (some customer_ids intentionally out of range to test left join) --- orders = pd.DataFrame({ "order_id": range(N_ORDERS), "customer_id": rng.integers(0, N_CUSTOMERS + 200, N_ORDERS), "product_id": rng.integers(0, N_PRODUCTS, N_ORDERS), "quantity": rng.integers(1, 20, N_ORDERS), "order_total": np.round(rng.uniform(5.0, 2000.0, N_ORDERS), 2), "channel": rng.choice(["web", "mobile", "store", "phone"], N_ORDERS), }) customers.to_csv("customers.csv", index=False) products.to_csv("products.csv", index=False) orders.to_csv("orders.csv", index=False) print(f"Generated {N_CUSTOMERS} customers, {N_PRODUCTS} products, {N_ORDERS} orders") if __name__ == "__main__": generate() -
multi_join.py 3.6 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Three-table join pipeline with aggregation. Joins orders with customers (left join) and products (inner join), then computes per-customer and per-category summaries. """ import numpy as np import pandas as pd from generate_data import generate def load_tables(): generate() orders = pd.read_csv("orders.csv") customers = pd.read_csv("customers.csv") products = pd.read_csv("products.csv") print(f"Loaded orders={len(orders)}, customers={len(customers)}, products={len(products)}") return orders, customers, products def join_tables(orders, customers, products): """Left-join orders->customers, then inner-join with products.""" # Left join: keep all orders even if customer_id is missing merged = orders.merge(customers, on="customer_id", how="left") print(f"After left join with customers: {len(merged)} rows, " f"{merged['customer_name'].isna().sum()} unmatched customers") # Inner join: drop orders whose product_id doesn't match merged = merged.merge(products, on="product_id", how="inner") print(f"After inner join with products: {len(merged)} rows") # Computed columns merged["line_total"] = merged["quantity"] * merged["base_price"] merged["total_weight"] = merged["quantity"] * merged["weight_kg"] merged["over_credit"] = (merged["order_total"] > merged["credit_limit"]).fillna(False) return merged def customer_summary(merged): """Per-customer aggregation.""" cust = ( merged.groupby("customer_id") .agg( num_orders=("order_id", "count"), total_spent=("order_total", "sum"), avg_order=("order_total", "mean"), unique_products=("product_id", "nunique"), total_weight=("total_weight", "sum"), times_over_credit=("over_credit", "sum"), tier=("tier", "first"), country=("country", "first"), ) .reset_index() .sort_values("total_spent", ascending=False) ) print(f"Customer summary: {len(cust)} customers") return cust def category_summary(merged): """Per-category aggregation.""" cat = ( merged.groupby("category") .agg( num_orders=("order_id", "count"), total_revenue=("line_total", "sum"), avg_quantity=("quantity", "mean"), unique_customers=("customer_id", "nunique"), avg_weight=("total_weight", "mean"), ) .reset_index() .sort_values("total_revenue", ascending=False) ) print(f"Category summary: {len(cat)} categories") return cat def tier_channel_summary(merged): """Cross-tabulation of tier x channel.""" cross = ( merged.groupby(["tier", "channel"]) .agg( order_count=("order_id", "count"), revenue=("line_total", "sum"), ) .reset_index() ) # Pivot to wide format pivot = cross.pivot_table( index="tier", columns="channel", values="revenue", aggfunc="sum", fill_value=0, ) print(f"Tier-channel pivot:\n{pivot}") return cross def main(): orders, customers, products = load_tables() merged = join_tables(orders, customers, products) cust_summary = customer_summary(merged) cat_summary = category_summary(merged) tier_ch = tier_channel_summary(merged) print(f"\nTop 5 customers by spend:\n{cust_summary.head(5).to_string(index=False)}") print(f"\nCategory breakdown:\n{cat_summary.to_string(index=False)}") if __name__ == "__main__": main()
-
-
-
cudf-native-stream-handoff-boundary
-
code
-
run_smoke.sh 464 B
#!/usr/bin/env bash # SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 set -euo pipefail script_dir="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" tmp_dir="$(mktemp -d "${TMPDIR:-/var/tmp}/threaded-handoff.XXXXXX")" trap 'rm -rf "$tmp_dir"' EXIT nvcc -std=c++17 -O2 "$script_dir/threaded_handoff.cu" -o "$tmp_dir/threaded_handoff" "$tmp_dir/threaded_handoff" -
threaded_handoff.cu 3.9 KB · in bundle
-
-
NOTICE.md 344 B
# Notice This task is an original synthetic fixture. No upstream source code was copied. It is inspired by public CUDA stream/event ordering guidance and public cuDF native/JVM wrapper concepts. The starter program is intentionally small so the task focuses on object readiness, cross-stream consumption, and device-memory lifetime ordering.
-
-
cudf-null-handling
-
code
-
generate_data.py 2.1 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Generate synthetic data with intentional null patterns.""" import os import numpy as np import pandas as pd SEED = 42 N_ROWS = 40_000 def generate(): if os.path.exists("messy_data.csv"): return rng = np.random.default_rng(SEED) df = pd.DataFrame({ "id": range(N_ROWS), "group": rng.choice(["A", "B", "C", "D"], N_ROWS), "temperature": rng.normal(22.0, 3.0, N_ROWS), "humidity": rng.uniform(20, 90, N_ROWS), "pressure": rng.normal(1013, 5, N_ROWS), "wind_speed": rng.exponential(10, N_ROWS), "visibility": rng.uniform(1, 30, N_ROWS), "uv_index": rng.integers(0, 12, N_ROWS).astype(float), "air_quality": rng.choice(["good", "moderate", "poor", "hazardous"], N_ROWS), "station_code": rng.choice(["ST01", "ST02", "ST03", "ST04", "ST05"], N_ROWS), }) # Introduce nulls with different patterns # Random scattered nulls (~15% each) for col in ["temperature", "humidity", "pressure"]: mask = rng.random(N_ROWS) < 0.15 df.loc[mask, col] = np.nan # Block nulls (sensor offline for stretches) for start in [5000, 15000, 28000]: df.loc[start:start + 500, "wind_speed"] = np.nan df.loc[start:start + 300, "visibility"] = np.nan # Correlated nulls (uv_index missing when visibility is low) low_vis = df["visibility"] < 5 df.loc[low_vis & (rng.random(N_ROWS) < 0.7), "uv_index"] = np.nan # String column nulls str_mask = rng.random(N_ROWS) < 0.10 df.loc[str_mask, "air_quality"] = np.nan df["temperature"] = df["temperature"].round(2) df["humidity"] = df["humidity"].round(1) df["pressure"] = df["pressure"].round(1) df["wind_speed"] = df["wind_speed"].round(2) df["visibility"] = df["visibility"].round(1) df.to_csv("messy_data.csv", index=False) null_pcts = df.isnull().mean() * 100 print(f"Generated {len(df)} rows with null percentages:\n{null_pcts.to_string()}") if __name__ == "__main__": generate() -
null_pipeline.py 4.7 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Null handling pipeline: detect, fill, drop, interpolate, and report. Demonstrates various pandas null-handling strategies on messy weather data. """ import numpy as np import pandas as pd from generate_data import generate def load_data(): generate() df = pd.read_csv("messy_data.csv") print(f"Loaded {len(df)} rows") print(f"Null counts:\n{df.isnull().sum()}") return df def analyze_nulls(df): """Build a null analysis report.""" null_counts = df.isnull().sum() null_pcts = df.isnull().mean() * 100 report = pd.DataFrame({ "null_count": null_counts, "null_pct": null_pcts.round(2), "dtype": df.dtypes, }) # Per-group null rates group_nulls = df.groupby("group").apply( lambda g: g.isnull().sum() ).reset_index() print(f"Null report:\n{report}") return report, group_nulls def fill_with_strategies(df): """Apply different fill strategies to different columns.""" filled = df.copy() # Scalar fill filled["uv_index"] = filled["uv_index"].fillna(0) # Dict fill (different values per column) filled = filled.fillna({ "air_quality": "unknown", "visibility": filled["visibility"].median(), }) # Forward fill for block-missing wind data filled["wind_speed"] = filled["wind_speed"].ffill() # Backward fill for any remaining at the start filled["wind_speed"] = filled["wind_speed"].bfill() # Group-specific mean fill for temperature group_means = df.groupby("group")["temperature"].transform("mean") filled["temperature"] = filled["temperature"].fillna(group_means) # Conditional fill: humidity depends on air_quality quality_median = df.groupby("air_quality")["humidity"].median() for quality, median_val in quality_median.items(): mask = filled["humidity"].isna() & (filled["air_quality"] == quality) filled.loc[mask, "humidity"] = median_val # Fill remaining humidity nulls with global median filled["humidity"] = filled["humidity"].fillna(filled["humidity"].median()) print(f"After fills, remaining nulls:\n{filled.isnull().sum()}") return filled def interpolate_pressure(df): """Interpolate pressure readings within each station.""" interp_frames = [] for station, group in df.groupby("station_code"): g = group.copy() g["pressure"] = g["pressure"].interpolate(method="linear", limit=10) g["pressure"] = g["pressure"].bfill().ffill() interp_frames.append(g) result = pd.concat(interp_frames, ignore_index=True) remaining = result["pressure"].isna().sum() print(f"After interpolation, {remaining} pressure nulls remain") return result def dropna_analysis(df): """Demonstrate dropna with various parameters.""" # Drop rows where all numeric columns are null numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist() dropped_all = df.dropna(subset=numeric_cols, how="all") print(f"dropna(how='all') on numeric: {len(df)} -> {len(dropped_all)}") # Drop rows where more than 3 columns are null dropped_thresh = df.dropna(thresh=len(df.columns) - 3) print(f"dropna(thresh={len(df.columns) - 3}): {len(df)} -> {len(dropped_thresh)}") # Drop rows with any null in key columns key_cols = ["temperature", "humidity", "pressure"] dropped_subset = df.dropna(subset=key_cols) print(f"dropna(subset={key_cols}): {len(df)} -> {len(dropped_subset)}") return dropped_subset def create_null_indicators(df): """Create boolean indicator columns for null patterns.""" indicator_cols = ["temperature", "humidity", "pressure", "wind_speed", "uv_index"] for col in indicator_cols: df[f"{col}_missing"] = df[col].isna().astype(int) df["total_missing"] = df[[f"{c}_missing" for c in indicator_cols]].sum(axis=1) df["has_any_missing"] = (df["total_missing"] > 0).astype(int) # Null pattern string df["null_pattern"] = "" for col in indicator_cols: df["null_pattern"] = df["null_pattern"] + df[f"{col}_missing"].astype(str) pattern_counts = df["null_pattern"].value_counts().head(10) print(f"\nTop null patterns:\n{pattern_counts}") return df def main(): df = load_data() analyze_nulls(df) df_with_indicators = create_null_indicators(df) dropped = dropna_analysis(df) filled = fill_with_strategies(df) result = interpolate_pressure(filled) print(f"\nFinal null check:\n{result.isnull().sum()}") print(f"\nSample rows:\n{result.head(5).to_string(index=False)}") if __name__ == "__main__": main()
-
-
-
cudf-parquet-io
-
code
-
generate_data.py 2 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Generate multiple parquet files simulating partitioned log data.""" import os import numpy as np import pandas as pd from pathlib import Path SEED = 42 N_PER_FILE = 10_000 N_FILES = 6 def generate(): outdir = Path("raw_logs") if outdir.exists() and len(list(outdir.glob("*.parquet"))) == N_FILES: return outdir.mkdir(exist_ok=True) rng = np.random.default_rng(SEED) endpoints = ["/api/users", "/api/orders", "/api/products", "/api/health", "/api/search", "/api/auth"] methods = ["GET", "POST", "PUT", "DELETE"] status_codes = [200, 201, 204, 301, 400, 401, 403, 404, 500, 502, 503] status_weights = [0.50, 0.10, 0.05, 0.02, 0.08, 0.05, 0.03, 0.07, 0.04, 0.03, 0.03] regions = ["us-east-1", "us-west-2", "eu-west-1", "ap-southeast-1"] for i in range(N_FILES): base_date = pd.Timestamp("2024-01-01") + pd.Timedelta(days=i * 5) timestamps = base_date + pd.to_timedelta( rng.integers(0, 5 * 86400, N_PER_FILE), unit="s" ) df = pd.DataFrame({ "timestamp": timestamps, "endpoint": rng.choice(endpoints, N_PER_FILE), "method": rng.choice(methods, N_PER_FILE, p=[0.6, 0.2, 0.1, 0.1]), "status_code": rng.choice(status_codes, N_PER_FILE, p=status_weights), "response_time_ms": np.round(rng.exponential(150, N_PER_FILE), 2), "bytes_sent": rng.integers(100, 50_000, N_PER_FILE), "user_id": rng.integers(1, 5_000, N_PER_FILE), "region": rng.choice(regions, N_PER_FILE), "is_cached": rng.choice([True, False], N_PER_FILE, p=[0.3, 0.7]), }) fname = outdir / f"logs_batch_{i:03d}.parquet" df.to_parquet(fname, index=False) print(f"Wrote {fname} ({len(df)} rows)") print(f"Generated {N_FILES} parquet files in {outdir}/") if __name__ == "__main__": generate() -
parquet_pipeline.py 4.3 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Parquet I/O pipeline: read multiple files, concatenate, filter, write partitioned. Reads log data from multiple parquet files, concatenates them, applies filters and transformations, then writes partitioned parquet output. """ import os import numpy as np import pandas as pd from pathlib import Path from generate_data import generate def load_all_parquet(input_dir): """Read all parquet files from a directory and concatenate.""" generate() parquet_files = sorted(Path(input_dir).glob("*.parquet")) print(f"Found {len(parquet_files)} parquet files in {input_dir}") frames = [] for f in parquet_files: df = pd.read_parquet(f) df["source_file"] = f.stem frames.append(df) combined = pd.concat(frames, ignore_index=True) print(f"Combined: {len(combined)} rows, {combined.columns.tolist()}") return combined def filter_and_transform(df): """Apply filters and add computed columns.""" # Filter out health check endpoints df = df[df["endpoint"] != "/api/health"].copy() print(f"After filtering health checks: {len(df)} rows") # Categorize status codes df["status_category"] = pd.cut( df["status_code"], bins=[0, 199, 299, 399, 499, 599], labels=["1xx", "2xx", "3xx", "4xx", "5xx"], ) # Performance buckets df["is_slow"] = (df["response_time_ms"] > 500).astype(int) df["perf_bucket"] = pd.cut( df["response_time_ms"], bins=[0, 50, 200, 500, 1000, float("inf")], labels=["fast", "normal", "slow", "very_slow", "timeout"], ) # Extract hour from timestamp df["hour"] = df["timestamp"].dt.hour df["day_of_week"] = df["timestamp"].dt.dayofweek return df def compute_summaries(df): """Compute endpoint and region summaries.""" endpoint_summary = df.groupby("endpoint").agg( request_count=("user_id", "count"), unique_users=("user_id", "nunique"), avg_response_ms=("response_time_ms", "mean"), p95_response_ms=("response_time_ms", lambda x: x.quantile(0.95)), error_count=("is_slow", "sum"), total_bytes=("bytes_sent", "sum"), ).reset_index() region_summary = df.groupby("region").agg( request_count=("user_id", "count"), avg_response_ms=("response_time_ms", "mean"), cache_hit_rate=("is_cached", "mean"), ).reset_index() print(f"Endpoint summary:\n{endpoint_summary.to_string(index=False)}") print(f"\nRegion summary:\n{region_summary.to_string(index=False)}") return endpoint_summary, region_summary def write_partitioned(df, output_dir): """Write partitioned parquet output by region.""" output_path = Path(output_dir) if output_path.exists(): import shutil shutil.rmtree(output_path) output_path.mkdir(parents=True) # Convert categoricals to string for parquet compatibility for col in df.select_dtypes(include=["category"]).columns: df[col] = df[col].astype(str) for region, group in df.groupby("region"): region_dir = output_path / f"region={region}" region_dir.mkdir(exist_ok=True) out_file = region_dir / "data.parquet" group.to_parquet(out_file, index=False) print(f"Wrote {out_file} ({len(group)} rows)") def write_summaries(endpoint_summary, region_summary, output_dir): """Write summary tables as parquet.""" output_path = Path(output_dir) output_path.mkdir(parents=True, exist_ok=True) endpoint_summary.to_parquet(output_path / "endpoint_summary.parquet", index=False) region_summary.to_parquet(output_path / "region_summary.parquet", index=False) print(f"Wrote summary parquets to {output_path}") def main(): df = load_all_parquet("raw_logs") df = filter_and_transform(df) endpoint_summary, region_summary = compute_summaries(df) write_partitioned(df, "processed_logs") write_summaries(endpoint_summary, region_summary, "processed_logs/summaries") # Verify round-trip by reading back read_back = pd.read_parquet("processed_logs/summaries/endpoint_summary.parquet") print(f"\nRound-trip verification: {len(read_back)} endpoint summary rows read back") if __name__ == "__main__": main()
-
-
-
cudf-pivot-melt
-
code
-
generate_data.py 1.4 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Generate synthetic retail sales data for pivot/melt operations.""" import os import numpy as np import pandas as pd SEED = 42 N_ROWS = 60_000 def generate(): if os.path.exists("retail_sales.csv"): return rng = np.random.default_rng(SEED) stores = [f"Store_{i:02d}" for i in range(1, 16)] products = ["Laptop", "Phone", "Tablet", "Headphones", "Charger", "Case", "Cable", "Monitor", "Keyboard", "Mouse"] quarters = ["Q1", "Q2", "Q3", "Q4"] years = [2022, 2023, 2024] channels = ["online", "in-store", "phone"] df = pd.DataFrame({ "transaction_id": range(N_ROWS), "store": rng.choice(stores, N_ROWS), "product": rng.choice(products, N_ROWS), "year": rng.choice(years, N_ROWS), "quarter": rng.choice(quarters, N_ROWS), "channel": rng.choice(channels, N_ROWS, p=[0.5, 0.35, 0.15]), "units_sold": rng.integers(1, 20, N_ROWS), "revenue": np.round(rng.uniform(10, 2000, N_ROWS), 2), "cost": np.round(rng.uniform(5, 1500, N_ROWS), 2), "customer_satisfaction": rng.integers(1, 6, N_ROWS), }) df["profit"] = df["revenue"] - df["cost"] df.to_csv("retail_sales.csv", index=False) print(f"Generated {len(df)} retail sales rows -> retail_sales.csv") if __name__ == "__main__": generate() -
reshape_analysis.py 4.6 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Pivot, melt, stack/unstack, and cross-tabulation on retail data. Demonstrates various DataFrame reshape operations for sales analysis. """ import numpy as np import pandas as pd from generate_data import generate def load_data(): generate() df = pd.read_csv("retail_sales.csv") print(f"Loaded {len(df)} retail sales rows") return df def pivot_revenue_by_product_quarter(df): """Pivot table: average revenue by product and quarter.""" pivot = pd.pivot_table( df, values="revenue", index="product", columns="quarter", aggfunc="mean", fill_value=0, ) pivot = pivot.round(2) print(f"Revenue pivot (product x quarter):\n{pivot}") return pivot def pivot_multi_agg(df): """Pivot table with multiple aggregation functions.""" pivot = pd.pivot_table( df, values=["revenue", "units_sold"], index=["store"], columns=["year"], aggfunc={"revenue": ["sum", "mean"], "units_sold": "sum"}, fill_value=0, ) print(f"Multi-agg pivot shape: {pivot.shape}") print(f"Columns: {pivot.columns.tolist()[:8]}...") return pivot def melt_pivot_back(pivot_df): """Melt a pivoted DataFrame back to long format.""" # Reset index to make product a column flat = pivot_df.reset_index() melted = pd.melt( flat, id_vars=["product"], var_name="quarter", value_name="avg_revenue", ) melted = melted.sort_values(["product", "quarter"]) print(f"Melted back to long format: {len(melted)} rows") return melted def stack_unstack_demo(df): """Demonstrate stack and unstack operations.""" # Create a multi-index aggregation agg = df.groupby(["store", "product"]).agg( total_revenue=("revenue", "sum"), total_units=("units_sold", "sum"), ) # Unstack product to columns unstacked = agg["total_revenue"].unstack(fill_value=0) print(f"Unstacked shape: {unstacked.shape}") # Stack it back stacked = unstacked.stack() stacked.name = "total_revenue" stacked = stacked.reset_index() print(f"Re-stacked: {len(stacked)} rows") return unstacked, stacked def crosstab_analysis(df): """Cross-tabulation of channel vs product.""" # Count cross-tab ct_count = pd.crosstab( df["channel"], df["product"], margins=True, margins_name="Total", ) print(f"Count crosstab:\n{ct_count}") # Value cross-tab (average satisfaction) ct_sat = pd.crosstab( df["channel"], df["product"], values=df["customer_satisfaction"], aggfunc="mean", ).round(2) print(f"\nSatisfaction crosstab:\n{ct_sat}") # Normalized cross-tab ct_norm = pd.crosstab( df["channel"], df["product"], normalize="index", ).round(4) print(f"\nNormalized crosstab:\n{ct_norm}") return ct_count, ct_sat, ct_norm def year_over_year_pivot(df): """Pivot to compare year-over-year performance by store.""" yearly = df.groupby(["store", "year"]).agg( revenue=("revenue", "sum"), units=("units_sold", "sum"), avg_profit=("profit", "mean"), ).reset_index() # Pivot years to columns for side-by-side comparison yoy = yearly.pivot_table( index="store", columns="year", values="revenue", aggfunc="sum", fill_value=0, ) yoy.columns = [f"revenue_{y}" for y in yoy.columns] yoy = yoy.reset_index() # Compute growth rates if "revenue_2023" in yoy.columns and "revenue_2022" in yoy.columns: yoy["growth_22_23"] = ( (yoy["revenue_2023"] - yoy["revenue_2022"]) / yoy["revenue_2022"] ).round(4) if "revenue_2024" in yoy.columns and "revenue_2023" in yoy.columns: yoy["growth_23_24"] = ( (yoy["revenue_2024"] - yoy["revenue_2023"]) / yoy["revenue_2023"] ).round(4) print(f"Year-over-year:\n{yoy.head().to_string(index=False)}") return yoy def main(): df = load_data() # Pivot operations revenue_pivot = pivot_revenue_by_product_quarter(df) multi_pivot = pivot_multi_agg(df) # Melt melted = melt_pivot_back(revenue_pivot) # Stack / Unstack stack_unstack_demo(df) # Cross-tabulation crosstab_analysis(df) # Year-over-year pivot yoy = year_over_year_pivot(df) print(f"\nAll reshape operations completed successfully.") if __name__ == "__main__": main()
-
-
-
cudf-string-ops
-
code
-
clean_contacts.py 3.4 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Text cleaning pipeline using pandas string operations. Reads messy contact data and applies a series of string transformations: lowercase, strip whitespace, regex extraction, contains checks, and replacements. """ import pandas as pd from generate_data import generate def load_data(): generate() df = pd.read_csv("raw_contacts.csv") df["notes"] = df["notes"].fillna("") print(f"Loaded {len(df)} raw contacts") return df def clean_names(df): """Normalize first and last names.""" df["first_name"] = df["first_name"].str.strip().str.lower().str.title() df["last_name"] = df["last_name"].str.strip().str.lower().str.title() df["full_name"] = df["first_name"] + " " + df["last_name"] return df def clean_emails(df): """Strip and lowercase emails, extract domain.""" df["email"] = df["email"].str.strip().str.lower() df["email_domain"] = df["email"].str.extract(r"@([a-z0-9\.\-]+)$", expand=False) df["is_company_email"] = df["email_domain"].str.contains( r"\.(org|net)$", regex=True ).astype(int) return df def normalize_phones(df): """Extract digits from phone numbers into a standard 10-digit format.""" digits = df["phone"].str.replace(r"[^\d]", "", regex=True) # Remove leading '1' for 11-digit US numbers digits = digits.str.replace(r"^1(\d{10})$", r"\1", regex=True) df["phone_clean"] = ( "(" + digits.str[:3] + ") " + digits.str[3:6] + "-" + digits.str[6:10] ) return df def parse_addresses(df): """Extract state and zip from address strings.""" df["address"] = df["address"].str.strip() df["state"] = df["address"].str.extract(r",\s*([A-Z]{2})\s+\d{5}", expand=False) df["zipcode"] = df["address"].str.extract(r"(\d{5})\s*$", expand=False) return df def process_notes(df): """Extract reference numbers, detect flags, clean up notes.""" df["notes"] = df["notes"].str.strip() # Extract reference numbers like Ref#12345 or REF#99887 df["ref_number"] = df["notes"].str.extract( r"[Rr][Ee][Ff]#(\d+)", expand=False ) # Flag rows df["is_vip"] = df["notes"].str.contains("VIP", case=False, na=False).astype(int) df["has_bounced"] = df["notes"].str.contains("BOUNCED", case=False, na=False).astype(int) df["needs_followup"] = df["notes"].str.contains( "follow-up|pending", case=False, regex=True, na=False ).astype(int) # Redact discount details df["notes_redacted"] = df["notes"].str.replace( r"Discount:\s*\d+%", "Discount: [REDACTED]", regex=True ) return df def summarize(df): """Print summary statistics about the cleaned data.""" print(f"\nCleaned {len(df)} contacts") print(f" Unique domains: {df['email_domain'].nunique()}") print(f" Company emails: {df['is_company_email'].sum()}") print(f" VIP customers: {df['is_vip'].sum()}") print(f" Bounced emails: {df['has_bounced'].sum()}") print(f" With ref numbers: {df['ref_number'].notna().sum()}") print(f" States found: {df['state'].nunique()}") def main(): df = load_data() df = clean_names(df) df = clean_emails(df) df = normalize_phones(df) df = parse_addresses(df) df = process_notes(df) summarize(df) df.to_csv("cleaned_contacts.csv", index=False) print("\nWrote cleaned_contacts.csv") if __name__ == "__main__": main() -
generate_data.py 2.7 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Generate synthetic messy text data for string operations.""" import os import numpy as np import pandas as pd SEED = 42 N_ROWS = 30_000 def generate(): if os.path.exists("raw_contacts.csv"): return rng = np.random.default_rng(SEED) first_names = ["Alice", "Bob", " Charlie", "Diana ", " Eve", "FRANK", "grace", " HANK ", "Ivy", " jack"] last_names = ["Smith", " JONES", "Williams ", " BROWN", "davis", " Miller", "WILSON ", "moore", " Taylor", "Anderson"] domains = ["gmail.com", "yahoo.com", "outlook.com", "company.org", "example.net"] phones_raw = [] emails_raw = [] addresses_raw = [] for _ in range(N_ROWS): # messy phone: mix of formats area = rng.integers(200, 999) mid = rng.integers(100, 999) last4 = rng.integers(1000, 9999) fmt = rng.choice(["paren", "dash", "dot", "plain", "intl"]) if fmt == "paren": phones_raw.append(f"({area}) {mid}-{last4}") elif fmt == "dash": phones_raw.append(f"{area}-{mid}-{last4}") elif fmt == "dot": phones_raw.append(f"{area}.{mid}.{last4}") elif fmt == "plain": phones_raw.append(f"{area}{mid}{last4}") else: phones_raw.append(f"+1-{area}-{mid}-{last4}") fn = rng.choice(first_names) ln = rng.choice(last_names) dom = rng.choice(domains) emails_raw.append(f" {fn.strip().lower()}.{ln.strip().lower()}@{dom} ") num = rng.integers(1, 9999) street = rng.choice(["Main St", "Oak Ave", "1st Blvd", "Elm Dr", "Pine Ln"]) state = rng.choice(["CA", "NY", "TX", "FL", "WA", "IL"]) zipcode = rng.integers(10000, 99999) addresses_raw.append(f" {num} {street}, {state} {zipcode} ") df = pd.DataFrame({ "first_name": rng.choice(first_names, N_ROWS), "last_name": rng.choice(last_names, N_ROWS), "email": emails_raw, "phone": phones_raw, "address": addresses_raw, "notes": rng.choice([ "VIP customer - priority support", "CALLED 2024-01-15: billing issue", "Ref#12345 - pending review", " no notes ", "email BOUNCED on 2024-03-01", "Discount: 20% off next order", "REF#99887 follow-up required", "", ], N_ROWS), }) df.to_csv("raw_contacts.csv", index=False) print(f"Generated {len(df)} messy contact rows -> raw_contacts.csv") if __name__ == "__main__": generate()
-
-
-
cudf-timeseries-resample
-
code
-
generate_data.py 1.5 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Generate synthetic sensor data with minute-level timestamps.""" import os import numpy as np import pandas as pd SEED = 42 N_MINUTES = 60_000 # ~41 days of minute-level data def generate(): if os.path.exists("sensor_data.csv"): return rng = np.random.default_rng(SEED) timestamps = pd.date_range( start="2024-01-01", periods=N_MINUTES, freq="min" ) # Simulate three sensors with seasonal patterns and noise hour_of_day = timestamps.hour + timestamps.minute / 60.0 day_cycle = np.sin(2 * np.pi * hour_of_day / 24.0) df = pd.DataFrame({ "timestamp": timestamps, "sensor_id": rng.choice(["S1", "S2", "S3"], N_MINUTES), "temperature": 20.0 + 5.0 * day_cycle + rng.normal(0, 0.5, N_MINUTES), "humidity": 60.0 - 10.0 * day_cycle + rng.normal(0, 2.0, N_MINUTES), "pressure": 1013.0 + rng.normal(0, 3.0, N_MINUTES), "voltage": 3.3 + rng.normal(0, 0.05, N_MINUTES), }) df["temperature"] = np.round(df["temperature"], 2) df["humidity"] = np.clip(np.round(df["humidity"], 1), 0, 100) df["pressure"] = np.round(df["pressure"], 1) df["voltage"] = np.round(df["voltage"], 3) df.to_csv("sensor_data.csv", index=False) print(f"Generated {len(df)} sensor readings -> sensor_data.csv") if __name__ == "__main__": generate() -
timeseries_analysis.py 4.1 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Timeseries resampling and rolling statistics pipeline. Reads minute-level sensor data, resamples to hourly and daily frequencies, and computes rolling window statistics for anomaly detection thresholds. """ import numpy as np import pandas as pd from generate_data import generate def load_data(): generate() df = pd.read_csv("sensor_data.csv", parse_dates=["timestamp"]) df = df.sort_values("timestamp") print(f"Loaded {len(df)} sensor readings from " f"{df['timestamp'].min()} to {df['timestamp'].max()}") return df def resample_hourly(df): """Resample each sensor to hourly frequency.""" hourly_frames = [] for sensor_id, group in df.groupby("sensor_id"): ts = group.set_index("timestamp") hourly = ts[["temperature", "humidity", "pressure", "voltage"]].resample("h").agg( ["mean", "min", "max", "std"] ) # Flatten multi-level columns hourly.columns = ["_".join(col) for col in hourly.columns] hourly["sensor_id"] = sensor_id hourly = hourly.reset_index() hourly_frames.append(hourly) result = pd.concat(hourly_frames, ignore_index=True) print(f"Hourly resampled: {len(result)} rows") return result def resample_daily(df): """Resample all sensors to daily frequency with aggregation.""" ts = df.set_index("timestamp") daily = ts.groupby("sensor_id").resample("D").agg({ "temperature": ["mean", "min", "max"], "humidity": ["mean", "min", "max"], "pressure": "mean", "voltage": "mean", }) daily.columns = ["_".join(col) for col in daily.columns] daily = daily.reset_index() print(f"Daily resampled: {len(daily)} rows") return daily def compute_rolling_stats(hourly): """Compute rolling 24-hour statistics on the hourly data.""" rolling_frames = [] for sensor_id, group in hourly.groupby("sensor_id"): g = group.sort_values("timestamp").copy() g["temp_rolling_mean_24h"] = ( g["temperature_mean"].rolling(window=24, min_periods=6).mean() ) g["temp_rolling_std_24h"] = ( g["temperature_mean"].rolling(window=24, min_periods=6).std() ) g["humidity_rolling_mean_24h"] = ( g["humidity_mean"].rolling(window=24, min_periods=6).mean() ) g["pressure_expanding_mean"] = g["pressure_mean"].expanding(min_periods=1).mean() # Anomaly flag: temperature deviates more than 2 std from rolling mean g["temp_anomaly"] = ( (g["temperature_mean"] - g["temp_rolling_mean_24h"]).abs() > 2 * g["temp_rolling_std_24h"] ).astype(int) rolling_frames.append(g) result = pd.concat(rolling_frames, ignore_index=True) anomaly_count = result["temp_anomaly"].sum() print(f"Rolling stats computed; {anomaly_count} temperature anomalies detected") return result def compute_daily_change(daily): """Compute day-over-day changes using shift.""" change_frames = [] for sensor_id, group in daily.groupby("sensor_id"): g = group.sort_values("timestamp").copy() g["temp_change"] = g["temperature_mean"] - g["temperature_mean"].shift(1) g["humidity_change"] = g["humidity_mean"] - g["humidity_mean"].shift(1) g["temp_cummax"] = g["temperature_max"].cummax() g["temp_cummin"] = g["temperature_min"].cummin() change_frames.append(g) result = pd.concat(change_frames, ignore_index=True) print(f"Daily changes computed for {result['sensor_id'].nunique()} sensors") return result def main(): df = load_data() hourly = resample_hourly(df) daily = resample_daily(df) hourly_with_rolling = compute_rolling_stats(hourly) daily_with_changes = compute_daily_change(daily) print(f"\nHourly sample:\n{hourly_with_rolling.head(3).to_string(index=False)}") print(f"\nDaily sample:\n{daily_with_changes.head(3).to_string(index=False)}") if __name__ == "__main__": main()
-
-
-
cudf-window-functions
-
code
-
generate_data.py 1.4 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Generate synthetic stock trading data for window function analysis.""" import os import numpy as np import pandas as pd SEED = 42 N_DAYS = 500 N_STOCKS = 50 def generate(): if os.path.exists("stock_trades.csv"): return rng = np.random.default_rng(SEED) dates = pd.bdate_range(start="2022-01-03", periods=N_DAYS) tickers = [f"STK{i:03d}" for i in range(N_STOCKS)] rows = [] for ticker in tickers: base_price = rng.uniform(10, 500) prices = [base_price] for _ in range(N_DAYS - 1): change = rng.normal(0, base_price * 0.02) prices.append(max(1.0, prices[-1] + change)) for i, date in enumerate(dates): rows.append({ "date": date, "ticker": ticker, "close": round(prices[i], 2), "volume": int(rng.integers(10_000, 5_000_000)), "high": round(prices[i] * (1 + rng.uniform(0, 0.03)), 2), "low": round(prices[i] * (1 - rng.uniform(0, 0.03)), 2), }) df = pd.DataFrame(rows) df["trade_value"] = df["close"] * df["volume"] df.to_csv("stock_trades.csv", index=False) print(f"Generated {len(df)} stock trade rows -> stock_trades.csv") if __name__ == "__main__": generate() -
window_analysis.py 5.1 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Window function analysis on stock trading data. Computes rankings, cumulative sums, rolling averages, expanding statistics, and shift/lag features for each stock ticker. """ import numpy as np import pandas as pd from generate_data import generate def load_data(): generate() df = pd.read_csv("stock_trades.csv", parse_dates=["date"]) df = df.sort_values(["ticker", "date"]) print(f"Loaded {len(df)} trades for {df['ticker'].nunique()} tickers") return df def add_rankings(df): """Rank stocks by close price and volume within each date.""" df["price_rank_dense"] = df.groupby("date")["close"].rank( method="dense", ascending=False ) df["price_rank_min"] = df.groupby("date")["close"].rank( method="min", ascending=False ) df["volume_rank"] = df.groupby("date")["volume"].rank( method="average", ascending=False ) df["price_pctrank"] = df.groupby("date")["close"].rank(pct=True) print(f"Rankings added; top stock on last day: " f"rank 1 = {df.loc[df['price_rank_dense'] == 1].tail(1)['ticker'].values}") return df def add_cumulative(df): """Compute cumulative statistics per ticker.""" df["cumsum_volume"] = df.groupby("ticker")["volume"].cumsum() df["cumsum_trade_value"] = df.groupby("ticker")["trade_value"].cumsum() df["cummax_close"] = df.groupby("ticker")["close"].cummax() df["cummin_close"] = df.groupby("ticker")["close"].cummin() df["cum_avg_close"] = df["cumsum_trade_value"] / df["cumsum_volume"] print("Cumulative stats added") return df def add_rolling_stats(df): """Compute rolling window statistics per ticker.""" rolling_frames = [] for ticker, group in df.groupby("ticker"): g = group.sort_values("date").copy() # 5-day and 20-day rolling averages g["sma_5"] = g["close"].rolling(window=5, min_periods=1).mean() g["sma_20"] = g["close"].rolling(window=20, min_periods=5).mean() # Rolling standard deviation (volatility) g["volatility_20"] = g["close"].rolling(window=20, min_periods=5).std() # Rolling min/max (support/resistance levels) g["rolling_high_20"] = g["high"].rolling(window=20, min_periods=5).max() g["rolling_low_20"] = g["low"].rolling(window=20, min_periods=5).min() # Rolling sum of volume g["volume_sum_10"] = g["volume"].rolling(window=10, min_periods=1).sum() rolling_frames.append(g) result = pd.concat(rolling_frames, ignore_index=True) print("Rolling stats added (SMA-5, SMA-20, volatility, support/resistance)") return result def add_expanding_stats(df): """Compute expanding window statistics per ticker.""" expanding_frames = [] for ticker, group in df.groupby("ticker"): g = group.sort_values("date").copy() g["expanding_mean"] = g["close"].expanding(min_periods=1).mean() g["expanding_std"] = g["close"].expanding(min_periods=2).std() g["expanding_max"] = g["close"].expanding(min_periods=1).max() g["expanding_min"] = g["close"].expanding(min_periods=1).min() expanding_frames.append(g) result = pd.concat(expanding_frames, ignore_index=True) print("Expanding stats added") return result def add_shift_features(df): """Compute lag/lead features and returns.""" shift_frames = [] for ticker, group in df.groupby("ticker"): g = group.sort_values("date").copy() # Lag features g["prev_close"] = g["close"].shift(1) g["prev_close_5"] = g["close"].shift(5) # Daily return g["daily_return"] = (g["close"] - g["prev_close"]) / g["prev_close"] # 5-day return g["return_5d"] = (g["close"] - g["prev_close_5"]) / g["prev_close_5"] # Lead (next day close) g["next_close"] = g["close"].shift(-1) # Diff g["close_diff"] = g["close"].diff() g["volume_diff"] = g["volume"].diff() shift_frames.append(g) result = pd.concat(shift_frames, ignore_index=True) print("Shift/lag features added (returns, diffs, leads)") return result def generate_signals(df): """Simple moving average crossover signals.""" df["sma_cross"] = (df["sma_5"] > df["sma_20"]).astype(int) df["signal_change"] = df.groupby("ticker")["sma_cross"].diff().fillna(0).astype(int) buy_signals = (df["signal_change"] == 1).sum() sell_signals = (df["signal_change"] == -1).sum() print(f"Signals: {buy_signals} buys, {sell_signals} sells") return df def main(): df = load_data() df = add_rankings(df) df = add_cumulative(df) df = add_rolling_stats(df) df = add_expanding_stats(df) df = add_shift_features(df) df = generate_signals(df) print(f"\nFinal shape: {df.shape}") sample = df[df["ticker"] == "STK000"].tail(5) print(f"\nSample (STK000 last 5 days):\n" f"{sample[['date', 'close', 'sma_5', 'sma_20', 'daily_return', 'price_rank_dense']].to_string(index=False)}") if __name__ == "__main__": main()
-
-
-
negative-deep-learning-training
-
code
-
train.py 1.4 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Small PyTorch training script.""" from __future__ import annotations import torch from torch import nn from torch.utils.data import DataLoader, TensorDataset class Model(nn.Module): def __init__(self) -> None: super().__init__() self.net = nn.Sequential( nn.Linear(1024, 4096), nn.ReLU(), nn.Linear(4096, 4096), nn.ReLU(), nn.Linear(4096, 10), ) def forward(self, x: torch.Tensor) -> torch.Tensor: return self.net(x) def main() -> None: device = "cuda" if torch.cuda.is_available() else "cpu" x = torch.randn(20_000, 1024) y = torch.randint(0, 10, (20_000,)) loader = DataLoader(TensorDataset(x, y), batch_size=64, shuffle=True, num_workers=0) model = Model().to(device) opt = torch.optim.AdamW(model.parameters(), lr=1e-3) loss_fn = nn.CrossEntropyLoss() for epoch in range(2): for xb, yb in loader: xb = xb.to(device) yb = yb.to(device) opt.zero_grad(set_to_none=True) loss = loss_fn(model(xb), yb) loss.backward() opt.step() print(f"epoch={epoch} loss={float(loss.detach().cpu()):.4f}") if __name__ == "__main__": main()
-
-
-
source-cudf-null-fillna-semantics
-
code
-
null_cleanup.py 1.4 KB
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 """Pandas nullable-value cleanup pipeline.""" from __future__ import annotations import numpy as np import pandas as pd def build_frame() -> pd.DataFrame: return pd.DataFrame( { "account": pd.Series([1, 2, None, 4, 5, None], dtype="Int64"), "region": pd.Series(["west", None, "east", "west", None, "east"], dtype="string"), "score": [0.8, np.nan, 0.3, 0.9, np.nan, 0.2], "tier": pd.Series(["gold", "silver", None, "gold", "bronze", None], dtype="string"), } ) def clean(frame: pd.DataFrame) -> pd.DataFrame: result = frame.copy() result["region"] = result["region"].fillna("unknown") result["tier"] = result["tier"].fillna("unassigned") result["score"] = result["score"].where(result["score"].notna(), result["score"].median()) result["high_score"] = result["score"] >= 0.75 grouped = ( result.groupby("region", dropna=False) .agg( accounts=("account", "count"), avg_score=("score", "mean"), high_count=("high_score", "sum"), ) .reset_index() .sort_values("region") ) return grouped def main() -> None: print(clean(build_frame()).to_string(index=False)) if __name__ == "__main__": main()
-
-
NOTICE.md 340 B
# Attribution This task is source-inspired by cuDF null-handling tests. - Source: https://github.com/NVIDIA/cudf/blob/235f69a6fcef/python/cudf/cudf/tests/dataframe/methods/test_fillna.py - Upstream project: RAPIDS cuDF - License: Apache-2.0 - Local changes: original pandas fixture written for benchmark scoring; no upstream code copied.
-
-
-
evals.json 21.1 KB
[ { "id": "cudf-apply-udf__generic", "question": "Task: Row-wise apply, applymap, and column-wise UDFs that should move to vectorized operations or Numba where appropriate\nTask folder: evals/files/cudf-apply-udf/\nPrompt variant: generic\n\nUser prompt: Help me run this on the GPU\n\nUse the provided starter workspace for this task. Modify the starter file(s) under the provided `code/` directory. Run the relevant smoke or validation command from that workspace when practical, and report the changed files and validation result.", "expected_skill": "accelerated-computing-cudf", "expected_script": null, "files": [ "evals/files/cudf-apply-udf/code/generate_data.py", "evals/files/cudf-apply-udf/code/udf_pipeline.py" ], "ground_truth": "A successful answer uses the provided cudf-apply-udf starter files, especially code/udf_pipeline.py, to migrate the pandas DataFrame workload to cuDF where supported. It replaces row-wise apply/applymap or column UDF logic with vectorized cuDF expressions, Numba-compatible GPU logic, or a narrow compatibility boundary, preserves representative pandas results, and reports validation performed or the runtime blocker.", "expected_behavior": [ "Uses the provided cudf-apply-udf starter workspace and edits code/udf_pipeline.py rather than giving generic advice.", "Replaces pandas row-wise apply/applymap or column UDF logic with vectorized cuDF expressions, Numba-compatible GPU logic, or a clearly documented CPU compatibility boundary.", "Preserves representative pandas results and reports the validation command run or the runtime blocker encountered." ] }, { "id": "cudf-csv-etl__generic", "question": "Task: Basic CSV ETL pipeline \u2014 read, filter, compute columns, groupby aggregate, write to parquet\nTask folder: evals/files/cudf-csv-etl/\nPrompt variant: generic\n\nUser prompt: Help me run this on the GPU\n\nUse the provided starter workspace for this task. Modify the starter file(s) under the provided `code/` directory. Run the relevant smoke or validation command from that workspace when practical, and report the changed files and validation result.", "expected_skill": "accelerated-computing-cudf", "expected_script": null, "files": [ "evals/files/cudf-csv-etl/code/etl_pipeline.py", "evals/files/cudf-csv-etl/code/generate_data.py" ], "ground_truth": "A successful answer uses the provided cudf-csv-etl starter files, especially code/etl_pipeline.py, to move CSV read, filtering, computed columns, groupby aggregation, and parquet output to cuDF. It preserves filter predicates, computed-column formulas, grouping keys, aggregate columns, generated data paths, output paths, and reports validation performed or the runtime blocker.", "expected_behavior": [ "Uses the provided cudf-csv-etl starter workspace and edits code/etl_pipeline.py rather than inventing a new pipeline.", "Moves CSV reading, filtering, computed columns, groupby aggregation, and parquet output to cuDF where supported.", "Preserves filter predicates, computed-column formulas, grouping keys, aggregate outputs, generated data paths, and output paths, then reports validation or the runtime blocker." ] }, { "id": "cudf-groupby-agg__generic", "question": "Task: Complex groupby with multiple agg functions, named aggregation, and transform\nTask folder: evals/files/cudf-groupby-agg/\nPrompt variant: generic\n\nUser prompt: Help me run this on the GPU\n\nUse the provided starter workspace for this task. Modify the starter file(s) under the provided `code/` directory. Run the relevant smoke or validation command from that workspace when practical, and report the changed files and validation result.", "expected_skill": "accelerated-computing-cudf", "expected_script": null, "files": [ "evals/files/cudf-groupby-agg/code/generate_data.py", "evals/files/cudf-groupby-agg/code/groupby_analysis.py" ], "ground_truth": "A successful answer uses the provided cudf-groupby-agg starter files, especially code/groupby_analysis.py, to run the DataFrame loading and groupby work with cuDF. It preserves grouping keys, sum, mean, std, count, nunique, named aggregation, transform semantics or a documented compatibility boundary, output column names, and reports validation performed or the runtime blocker.", "expected_behavior": [ "Uses the provided cudf-groupby-agg starter workspace and edits code/groupby_analysis.py.", "Runs DataFrame loading and groupby work with cuDF while preserving grouping keys, sum, mean, std, count, nunique, named aggregation, and output column names.", "Handles transform semantics with cuDF or documents a compatibility boundary, and reports validation performed or the runtime blocker." ] }, { "id": "cudf-multi-join__generic", "question": "Task: Three-table join (orders, customers, products) with left/inner joins followed by aggregation\nTask folder: evals/files/cudf-multi-join/\nPrompt variant: generic\n\nUser prompt: Help me run this on the GPU\n\nUse the provided starter workspace for this task. Modify the starter file(s) under the provided `code/` directory. Run the relevant smoke or validation command from that workspace when practical, and report the changed files and validation result.", "expected_skill": "accelerated-computing-cudf", "expected_script": null, "files": [ "evals/files/cudf-multi-join/code/generate_data.py", "evals/files/cudf-multi-join/code/multi_join.py" ], "ground_truth": "A successful answer uses the provided cudf-multi-join starter files, especially code/multi_join.py, to migrate the orders, customers, and products joins plus downstream filtering and aggregation to cuDF. It preserves left and inner join types, join keys, suffix behavior, row-count expectations, post-join filters, output schema, and reports validation performed or the runtime blocker.", "expected_behavior": [ "Uses the provided cudf-multi-join starter workspace and edits code/multi_join.py.", "Migrates the orders, customers, and products joins plus downstream filtering and aggregation to cuDF where supported.", "Preserves join types, join keys, suffix behavior, row-count expectations, post-join filters, and output schema, then reports validation or the runtime blocker." ] }, { "id": "cudf-null-handling__generic", "question": "Task: DataFrame with many nulls \u2014 fillna strategies, dropna, interpolate, isna masks, conditional fills\nTask folder: evals/files/cudf-null-handling/\nPrompt variant: generic\n\nUser prompt: Help me run this on the GPU\n\nUse the provided starter workspace for this task. Modify the starter file(s) under the provided `code/` directory. Run the relevant smoke or validation command from that workspace when practical, and report the changed files and validation result.", "expected_skill": "accelerated-computing-cudf", "expected_script": null, "files": [ "evals/files/cudf-null-handling/code/generate_data.py", "evals/files/cudf-null-handling/code/null_pipeline.py" ], "ground_truth": "A successful answer uses the provided cudf-null-handling starter files, especially code/null_pipeline.py, to move null detection, fill, drop, mask, and conditional fill logic to cuDF where supported. It preserves scalar and dictionary fill rules, subset and threshold drop rules, NA-aware boolean masks, interpolation or other compatibility boundaries, and reports validation performed or the runtime blocker.", "expected_behavior": [ "Uses the provided cudf-null-handling starter workspace and edits code/null_pipeline.py.", "Moves null detection, fill, drop, mask, and conditional fill logic to cuDF where supported.", "Preserves scalar and dictionary fill rules, subset and threshold drop rules, NA-aware masks, and any interpolation compatibility boundary, then reports validation or the runtime blocker." ] }, { "id": "cudf-parquet-io__generic", "question": "Task: Read multiple parquet files, concatenate, filter, write partitioned output\nTask folder: evals/files/cudf-parquet-io/\nPrompt variant: generic\n\nUser prompt: Help me run this on the GPU\n\nUse the provided starter workspace for this task. Modify the starter file(s) under the provided `code/` directory. Run the relevant smoke or validation command from that workspace when practical, and report the changed files and validation result.", "expected_skill": "accelerated-computing-cudf", "expected_script": null, "files": [ "evals/files/cudf-parquet-io/code/generate_data.py", "evals/files/cudf-parquet-io/code/parquet_pipeline.py" ], "ground_truth": "A successful answer uses the provided cudf-parquet-io starter files, especially code/parquet_pipeline.py, to migrate parquet reads, concatenation, filtering, column selection, dtype handling, and parquet writes to cuDF. It preserves multi-file input handling, partitioned output behavior, generated data paths, output paths, and reports validation performed or the runtime blocker.", "expected_behavior": [ "Uses the provided cudf-parquet-io starter workspace and edits code/parquet_pipeline.py.", "Migrates parquet reads, concatenation, filtering, column selection, dtype handling, and parquet writes to cuDF where supported.", "Preserves multi-file input handling, partitioned output behavior, generated data paths, and output paths, then reports validation or the runtime blocker." ] }, { "id": "cudf-pivot-melt__generic", "question": "Task: Pivot table creation, melt/unpivot, stack/unstack, and cross-tabulation\nTask folder: evals/files/cudf-pivot-melt/\nPrompt variant: generic\n\nUser prompt: Help me run this on the GPU\n\nUse the provided starter workspace for this task. Modify the starter file(s) under the provided `code/` directory. Run the relevant smoke or validation command from that workspace when practical, and report the changed files and validation result.", "expected_skill": "accelerated-computing-cudf", "expected_script": null, "files": [ "evals/files/cudf-pivot-melt/code/generate_data.py", "evals/files/cudf-pivot-melt/code/reshape_analysis.py" ], "ground_truth": "A successful answer uses the provided cudf-pivot-melt starter files, especially code/reshape_analysis.py, to move supported reshape operations such as pivot, melt, stack/unstack, or crosstab-style logic to cuDF where practical. It preserves index labels, column labels, fill values, aggregation choices, output schema, compatibility boundaries, and reports validation performed or the runtime blocker.", "expected_behavior": [ "Uses the provided cudf-pivot-melt starter workspace and edits code/reshape_analysis.py.", "Moves supported reshape operations such as pivot, melt, stack/unstack, or crosstab-style logic to cuDF where practical.", "Preserves index labels, column labels, fill values, aggregation choices, output schema, and any compatibility boundary, then reports validation or the runtime blocker." ] }, { "id": "cudf-string-ops__generic", "question": "Task: Text cleaning pipeline using pandas string accessor \u2014 lowercase, strip, regex extract, contains, replace\nTask folder: evals/files/cudf-string-ops/\nPrompt variant: generic\n\nUser prompt: Help me run this on the GPU\n\nUse the provided starter workspace for this task. Modify the starter file(s) under the provided `code/` directory. Run the relevant smoke or validation command from that workspace when practical, and report the changed files and validation result.", "expected_skill": "accelerated-computing-cudf", "expected_script": null, "files": [ "evals/files/cudf-string-ops/code/clean_contacts.py", "evals/files/cudf-string-ops/code/generate_data.py" ], "ground_truth": "A successful answer uses the provided cudf-string-ops starter files, especially code/clean_contacts.py, to migrate string cleaning to cuDF string accessors for lowercase, strip, contains, replace, and extract-style operations. It preserves regex patterns, extracted columns, null handling, string dtype behavior, representative cleaned values, and reports validation performed or the runtime blocker.", "expected_behavior": [ "Uses the provided cudf-string-ops starter workspace and edits code/clean_contacts.py.", "Migrates string cleaning to cuDF string accessors for lowercase, strip, contains, replace, and extract-style operations where supported.", "Preserves regex patterns, extracted columns, null handling, string dtype behavior, and representative cleaned values, then reports validation or the runtime blocker." ] }, { "id": "cudf-timeseries-resample__generic", "question": "Task: Timestamped sensor data with resample to hourly/daily and rolling statistics\nTask folder: evals/files/cudf-timeseries-resample/\nPrompt variant: generic\n\nUser prompt: Help me run this on the GPU\n\nUse the provided starter workspace for this task. Modify the starter file(s) under the provided `code/` directory. Run the relevant smoke or validation command from that workspace when practical, and report the changed files and validation result.", "expected_skill": "accelerated-computing-cudf", "expected_script": null, "files": [ "evals/files/cudf-timeseries-resample/code/generate_data.py", "evals/files/cudf-timeseries-resample/code/timeseries_analysis.py" ], "ground_truth": "A successful answer uses the provided cudf-timeseries-resample starter files, especially code/timeseries_analysis.py, to run datetime parsing, timestamp ordering, bucket creation, aggregation, and rolling computations with cuDF where supported. It preserves hourly and daily grouping semantics, missing buckets, rolling window sizes, output ordering, compatibility boundaries, and reports validation performed or the runtime blocker.", "expected_behavior": [ "Uses the provided cudf-timeseries-resample starter workspace and edits code/timeseries_analysis.py.", "Runs datetime parsing, timestamp ordering, bucket creation, aggregation, and rolling computations with cuDF where supported.", "Preserves hourly and daily grouping semantics, missing buckets, rolling window sizes, output ordering, and any compatibility boundary, then reports validation or the runtime blocker." ] }, { "id": "cudf-window-functions__generic", "question": "Task: Ranking, cumulative sums, rolling averages, expanding stats, and shift/lag operations\nTask folder: evals/files/cudf-window-functions/\nPrompt variant: generic\n\nUser prompt: Help me run this on the GPU\n\nUse the provided starter workspace for this task. Modify the starter file(s) under the provided `code/` directory. Run the relevant smoke or validation command from that workspace when practical, and report the changed files and validation result.", "expected_skill": "accelerated-computing-cudf", "expected_script": null, "files": [ "evals/files/cudf-window-functions/code/generate_data.py", "evals/files/cudf-window-functions/code/window_analysis.py" ], "ground_truth": "A successful answer uses the provided cudf-window-functions starter files, especially code/window_analysis.py, to migrate ranking, cumulative operations, rolling calculations, expanding calculations, and shift/lag work to cuDF where supported. It preserves group keys, ordering columns, rank methods, window sizes, edge and null behavior, output names, and reports validation performed or the runtime blocker.", "expected_behavior": [ "Uses the provided cudf-window-functions starter workspace and edits code/window_analysis.py.", "Migrates ranking, cumulative operations, rolling calculations, expanding calculations, and shift/lag work to cuDF where supported.", "Preserves group keys, ordering columns, rank methods, window sizes, edge and null behavior, and output names, then reports validation or the runtime blocker." ] }, { "id": "source-cudf-null-fillna-semantics__generic", "question": "Task: Preserve pandas nullable dtype and fillna semantics while migrating to cuDF.\nTask folder: evals/files/source-cudf-null-fillna-semantics/\nPrompt variant: generic\n\nUser prompt: Help me move this DataFrame cleanup to the GPU without messing up missing values.\n\nUse the provided starter workspace for this task. Modify the starter file(s) under the provided `code/` directory. Run the relevant smoke or validation command from that workspace when practical, and report the changed files and validation result.", "expected_skill": "accelerated-computing-cudf", "expected_script": null, "files": [ "evals/files/source-cudf-null-fillna-semantics/NOTICE.md", "evals/files/source-cudf-null-fillna-semantics/code/null_cleanup.py" ], "ground_truth": "A successful answer uses the provided source-cudf-null-fillna-semantics starter files, especially code/null_cleanup.py, to migrate the cleanup workflow to cuDF without changing missing-value meaning. It preserves nullable integer, string, category-like, mask/where, fillna, and groupby semantics without lossy sentinel conversions, includes or describes pandas-versus-cuDF parity validation, and reports validation performed or the runtime blocker.", "expected_behavior": [ "Uses the provided source-cudf-null-fillna-semantics starter workspace and edits code/null_cleanup.py.", "Migrates the cleanup workflow to cuDF without changing missing-value meaning for nullable integers, strings, categories, masks, fillna, and groupby operations.", "Includes or describes pandas-versus-cuDF parity validation and reports validation performed or the runtime blocker." ] }, { "id": "cudf-native-stream-handoff-boundary__generic", "question": "Task: Fix a threaded native GPU wrapper so cross-stream handoff and close/free ordering are correct.\nTask folder: evals/files/cudf-native-stream-handoff-boundary/\nPrompt variant: generic\n\nUser prompt: This threaded GPU wrapper sometimes returns stale checksums after one\nworker hands a device buffer to another. Can you make the handoff correct\nwithout blocking the whole device on every transfer, and keep cleanup safe\nfor queued GPU work?\n\nUse the provided starter workspace for this task. Modify the starter file(s) under the provided `code/` directory. Run the relevant smoke or validation command from that workspace when practical, and report the changed files and validation result.", "expected_skill": "accelerated-computing-cudf", "expected_script": null, "files": [ "evals/files/cudf-native-stream-handoff-boundary/NOTICE.md", "evals/files/cudf-native-stream-handoff-boundary/code/run_smoke.sh", "evals/files/cudf-native-stream-handoff-boundary/code/threaded_handoff.cu" ], "ground_truth": "A successful answer uses the provided cudf-native-stream-handoff-boundary starter files, especially code/threaded_handoff.cu, to fix cross-thread or cross-stream GPU handoff by tying CUDA event readiness to the object dependency. It orders consumer work after producer writes, orders destruction or free after last stream use, preserves asynchronous overlap where practical, and reports compile or smoke validation performed or the runtime blocker.", "expected_behavior": [ "Uses the provided cudf-native-stream-handoff-boundary starter workspace and edits code/threaded_handoff.cu.", "Fixes cross-thread or cross-stream GPU handoff by tying CUDA event readiness to the object dependency without adding unnecessary device-wide blocking.", "Orders consumer work after producer writes and destruction/free after last stream use, then reports compile or smoke validation performed or the runtime blocker." ] }, { "id": "negative-deep-learning-training__generic", "question": "Task: Assess whether a PyTorch training performance issue belongs in NVIDIA GPU data science migration guidance.\nTask folder: evals/files/negative-deep-learning-training/\nPrompt variant: generic\n\nUser prompt: This PyTorch training script underutilizes my H100. Help me speed up model\ntraining on the GPU.\n\nUse the provided starter workspace for this task. Modify the starter file(s) under the provided `code/` directory. Run the relevant smoke or validation command from that workspace when practical, and report the changed files and validation result.", "expected_skill": null, "expected_script": null, "files": [ "evals/files/negative-deep-learning-training/code/train.py" ], "ground_truth": "A successful answer treats the provided train.py context as a PyTorch/deep-learning training performance task rather than a cuDF migration. It keeps guidance focused on model training, data loading, batching, mixed precision, profiling, or other training-specific tactics, and only mentions cuDF as optional upstream tabular ETL when that is directly relevant.", "expected_behavior": [ "Identifies the task as PyTorch or deep-learning training performance work rather than a cuDF DataFrame migration.", "Keeps guidance focused on model training, data loading, batching, mixed precision, profiling, or other training-specific tactics.", "Avoids invoking the cuDF skill except for an explicitly optional upstream tabular ETL note when directly relevant." ] } ]
-
-
references
-
api-patterns.md 6.9 KB
# cuDF API Patterns, Gaps, and Semantic Differences ## Key Semantic Differences from pandas ### Null/NaN Handling cuDF preserves nullable dtypes more often than pandas and uses Arrow-style nulls instead of float `NaN` promotion for nullable numeric columns: ```python import cudf import pandas as pd s = cudf.Series([1, None, 3]) print(s.dtype) # Int64 (nullable), not float64 with NaN # Check for null s.isnull() # works as expected s.isna() # equivalent # Fill nulls s.fillna(0) # works ``` Difference: `pd.Series([1, None, 3])` → dtype `float64` with `NaN`; cuDF → nullable `Int64` with `<NA>`. For string columns in current releases, missing string values display as `None` rather than `<NA>`. Do not write tests that depend on the display repr; compare with `.isna()`, `.notna()`, or typed result values. When comparing cuDF output with a pandas nullable reference, convert with `nullable=True`: ```python actual_pdf = gdf.to_pandas(nullable=True) ``` This keeps nullable pandas dtypes when they exist, instead of converting nulls to `np.nan` or `None` during the comparison boundary. For a null-heavy workflow, keep the pandas behavior as a compact reference and make the GPU path explicit: - scalar, dictionary, forward, and backward fills map directly to cuDF - group-specific fills are usually `groupby().transform(...)` followed by `fillna(...)` - conditional fills are boolean masks plus assignment, or a grouped aggregate merged back onto the original frame - linear interpolation is a semantic boundary; use cuDF only after checking the installed API behavior, or keep that narrow step under `cudf.pandas` with a parity check Validate row count, null count by column, representative filled values, grouped aggregates, and any rows produced by sort/interpolation-sensitive code. ### Sort Stability cuDF sort is **not stable by default**: ```python # Unstable (default) — faster df.sort_values("col") # Stable — required when sort order must match pandas exactly df.sort_values("col", stable=True) ``` ### String Operations — RE2 Regex cuDF uses RE2 (not Python's `re` / PCRE). Some patterns differ: ```python # RE2 does not support: # - Lookahead/lookbehind: (?=...), (?!...) # - Backreferences: \1 # - Possessive quantifiers: ?+, *+ # RE2-compatible (works): df["col"].str.contains(r"\d+") df["col"].str.replace(r"[aeiou]", "", regex=True) # Not RE2-compatible (will fail or fall back): df["col"].str.contains(r"(?=.*foo)") # lookahead — use different approach ``` ### CuPy Array Output When you access `.values` on a cuDF Series/DataFrame, you get a CuPy array (not NumPy): ```python import cudf import cupy as cp df = cudf.DataFrame({"a": [1, 2, 3]}) arr = df["a"].values # CuPy array, not NumPy! type(arr) # <class 'cupy.ndarray'> # To get NumPy explicitly: np_arr = df["a"].to_numpy() np_arr = cp.asnumpy(arr) ``` ## Common API Gaps and Workarounds The pandas API surface is vast and cuDF only covers a limited subset of it. This section lays out some of the common gaps but it should not be construed as an exhaustive list of discrepancies between the cuDF and pandas APIs. ### Operations Not Yet in cuDF | pandas Operation | Status | Workaround | |---|---|---| | `df.apply(func, axis=0)` | Column-wise apply: limited | Rewrite as vectorized cuDF ops | | `df.apply(func, axis=1)` | Row-wise apply: limited | Use `df.apply()` for simple funcs; otherwise `cudf.pandas` fallback | | Some `pd.Grouper` options | Partial | Use resample or direct groupby | | `pd.read_html()` | Not supported | Use pandas, then `cudf.from_pandas()` | | `pd.ExcelWriter` / `read_excel` | Not supported | Convert to CSV/Parquet first | | `df.to_sql()` | Not supported | Convert to pandas, then use pandas | | Multi-level columns (MultiIndex) | Partial | Flatten column names first | ### Reshape and Crosstab Fidelity `cudf.pivot_table`, `cudf.melt`, `cudf.crosstab`, `DataFrame.unstack`, and `DataFrame.stack` cover many reshape workflows. Treat the source pandas schema as observable behavior when a pipeline depends on reshape output: - Capture expected index labels, column labels or levels, names, shape, and representative values from the pandas path before rewriting. - Preserve pandas MultiIndex columns when the downstream code consumes them. If a flat schema is the practical cuDF representation, return a documented mapping such as `revenue_sum_2024` and validate consumers against that schema. - For multi-aggregation `pivot_table` outputs, keep aggregation names in the schema. Build the cuDF result from explicit grouped aggregations when needed, then either recreate the pandas column levels or flatten with deterministic names such as `{value}_{agg}_{column}`. - Implement missing `crosstab` conveniences with explicit GPU operations: counts via `cudf.crosstab`, margins via row/column sums, and row-normalized values by dividing each row by its row total. - Use `cudf.pandas` as a compatibility-first path when exact pandas reshape semantics are the goal and explicit cuDF would require broad schema changes. - Add a reusable validation helper that compares shape, index/column labels, aggregation names, null placement, and numeric values against the pandas reference on a small fixture. ### Time-Series and Rolling Fidelity cuDF supports datetime columns, sorting, grouped operations, shifts, cumulative operations, and many rolling-window patterns. Preserve pandas-visible time semantics when rewriting: - keep timezone, timestamp dtype, frequency, and bucket labels as part of the output contract - sort by grouping keys and timestamp before grouped `shift`, `rolling`, cumulative, or expanding-style calculations - validate sparse or missing buckets against the pandas reference; explicitly materialize the desired bucket grid when downstream consumers expect empty periods - use final `.to_pandas()` only for display, plotting, or reference comparison ### I/O Formats Supported by cuDF ```python # Fully supported (fast GPU I/O) cudf.read_csv(), cudf.read_parquet(), cudf.read_json() cudf.read_orc(), cudf.read_feather(), cudf.read_avro() # Not supported (use pandas, convert with cudf.from_pandas()) # Excel, HTML, SQL, HDF5, SAS, Stata, pickle ``` ## Useful cuDF-Specific APIs ```python # Convert between pandas and cuDF cudf_df = cudf.from_pandas(pd_df) pd_df = cudf_df.to_pandas() # Interop with CuPy import cupy as cp arr = cp.asarray(df["col"]) # zero-copy view df["new_col"] = cudf.Series(arr) # back to cuDF ``` ## Performance Tips 1. **Cast to float32 early**: `df[numeric_cols] = df[numeric_cols].astype("float32")` 2. **Use `cudf.read_parquet()` not CSV**: Parquet is columnar and dramatically faster to read 3. **Avoid `.apply()` with Python lambdas**: Use built-in cuDF ops instead 4. **Use `persist()` with dask-cuDF**: keeps computed data on GPU workers to avoid recomputation 5. **Avoid mid-pipeline `.to_pandas()`**: each roundtrip is a PCIe transfer -
cudf-pandas-accelerator.md 3.6 KB
# cudf.pandas Accelerator — Deep Dive ## How It Works `cudf.pandas` replaces the pandas module with a proxy that routes operations to cuDF when supported, falling back to standard pandas on CPU silently for unsupported operations. The fallback is transparent — code continues to work correctly, but unsupported ops run on CPU. ## Activation Methods | Method | Use Case | |---|---| | `%load_ext cudf.pandas` | Jupyter/IPython notebooks | | `python -m cudf.pandas script.py` | CLI script execution | | `import cudf.pandas; cudf.pandas.install()` | Programmatic, multiprocessing | **Critical**: Activation must happen BEFORE any pandas import, direct or transitive. If you're using IPython and pandas was already imported in the kernel, restart and run activation first. Direct usage of `cudf.pandas.install()` in a script cannot be undone and the script must be restarted. ## Profiling for GPU vs CPU Ops ### Cell-Level Profiling (Jupyter) ```python %load_ext cudf.pandas import pandas as pd %%cudf.pandas.profile df = pd.read_csv("data.csv") result = df.groupby("category")["amount"].sum() df.merge(lookup, on="id") ``` Output shows each operation's execution path (GPU or CPU) and time. ### Line-Level Profiling ```python %%cudf.pandas.line_profile df = pd.DataFrame({"a": range(1000000), "b": range(1000000)}) result = df.groupby("a")["b"].sum() # shows GPU time df.apply(lambda x: x + 1, axis=1) # shows CPU fallback time ``` ### CLI Profiling ```bash python -m cudf.pandas --profile my_script.py ``` ### Detecting Silent Fallbacks The profiling tools are also a convenient way to detect silent fallback. If the profiles show tasks running on the CPU unexpectedly, you may be hitting unsupported GPU methods (limitations are discussed in depth in the api-patterns.md reference file). Try reproducing with raw cudf code without cudf.pandas to verify. ## Verifying GPU Is Actually Used ```python # Method 1: Run nvidia-smi during execution # nvidia-smi dmon -s u -d 1 # Method 2: Check cudf.pandas stats import cudf.pandas stats = cudf.pandas.get_stats() print(stats) # shows GPU vs CPU operation counts ``` If GPU utilization stays 0% during execution, the entire workload fell back. Diagnose with `%%cudf.pandas.profile`. ## multiprocessing Support ```python # This pattern ensures workers also use cudf.pandas import cudf.pandas cudf.pandas.install() # must be FIRST, before everything else from multiprocessing import Pool import pandas as pd def process_chunk(args): # Workers inherit cudf.pandas installation df = pd.read_csv(args) return df.groupby("key")["value"].sum() with Pool(4) as pool: results = pool.map(process_chunk, file_list) ``` ## Limitations - **Usage of the NumPy C API**: Many projects have custom extension modules that interface with pandas dataframes via the NumPy C API for interacting with individual pandas columns. That will never work with cudf.pandas. - **Subclassed DataFrames**: code that subclasses `pd.DataFrame` may not work with cudf.pandas proxy - **Private pandas APIs** (`pd._libs.*`, etc.): not supported - **In-place operations with external code**: if third-party code holds references to pandas internals, proxy may not intercept correctly - **cudf.pandas does not speed up Python-level loops**: vectorize first, then accelerate ## When to Move to Explicit cuDF Move from cudf.pandas to explicit cuDF when: 1. Profile shows >30% CPU fallback rate on hot paths 2. You need cuDF-specific features (e.g., `cudf.set_option("spill", True)`) 3. You need explicit control over dtype casting (float32 optimization) 4. You're building a cuDF-first library, not accelerating existing pandas code -
dask-cudf-patterns.md 7 KB
# dask-cuDF Patterns ## Preferred API: dask.dataframe Backend (release 24.06+) The recommended way to use dask-cuDF is via the `dask.dataframe` backend config, **not** `import dask_cudf` directly. The backend API enables the query planning optimizer (predicate pushdown, projection pushdown) introduced in release 24.06+. ```python import dask dask.config.set({"dataframe.backend": "cudf"}) import dask.dataframe as dd # Read — now GPU-backed with query planning ddf = dd.read_parquet("data/*.parquet") ddf = dd.read_csv("data/*.csv") # All standard dask.dataframe operations work result = ddf.groupby("key")["value"].sum() ``` **Explicit `dask_cudf` import is still valid** but bypasses query planning: ```python import dask_cudf # works, but no optimizer — use for legacy code only ddf = dask_cudf.read_parquet("data/*.parquet") ``` ## Cluster Setup Always use `LocalCUDACluster`, even for a single GPU — it pins GPU affinity, enables the dashboard, and is required for proper spill configuration: ```python from dask_cuda import LocalCUDACluster from dask.distributed import Client import dask dask.config.set({"dataframe.backend": "cudf"}) # Standard setup — one worker per GPU cluster = LocalCUDACluster( enable_cudf_spill=True, # cuDF-native spill; preferred over device_memory_limit rmm_pool_size=0.8, # leave headroom for non-RMM allocations ) client = Client(cluster) # With UCX automatic transport selection for communication-heavy workloads cluster = LocalCUDACluster( enable_cudf_spill=True, rmm_pool_size=0.8, protocol="ucx", ) ``` ## Partition Sizing Partition size is the most impactful tuning parameter: | Workload | Target Partition Size | |---|---| | General ETL | 1/32 – 1/8 of single GPU memory | | Shuffle-intensive (groupby, join, sort) | 1/32 – 1/16 of GPU memory | ```python # Check current partitions print(f"Partitions: {ddf.npartitions}") # Tune at read time (most efficient) ddf = dd.read_parquet("data/", blocksize="256MB") # adjust to hit target partition size # Repartition after load if needed ddf = ddf.repartition(npartitions=64) ``` ## Reading Data ### Local Parquet (Recommended) ```python import dask.dataframe as dd # Project only needed columns — pushed down to storage ddf = dd.read_parquet("data/*.parquet", columns=["col1", "col2", "key"]) # aggregate_files=True merges small files into larger partitions ddf = dd.read_parquet("data/", aggregate_files=True, blocksize="512MB") ``` ### Remote Storage (S3, GCS) ```python # Use blocksize=None to avoid slow metadata collection on remote stores ddf = dd.read_parquet( "s3://bucket/prefix/", blocksize=None, filesystem="arrow", # pyarrow filesystem for S3/GCS columns=["col1", "col2"], ) ``` ## Aggregation Patterns ### Low-cardinality groupby ```python # split_out=1 avoids unnecessary shuffle for few output groups result = ddf.groupby("status_code")["amount"].sum(split_out=1) ``` ### High-cardinality groupby (default) ```python result = ddf.groupby("customer_id").agg({"amount": "sum", "count": "count"}) ``` ## Join / Merge Patterns ```python # Standard join (both datasets distributed) merged = large_ddf.merge(other_large_ddf, on="id", how="left") # Small table join: broadcast=True avoids shuffling the large table merged = large_ddf.merge( small_lookup_df, # cuDF DataFrame or small dask-cuDF on="id", how="left", broadcast=True, # sends small_lookup to all workers; no shuffle ) ``` ## Sort vs. Shuffle ```python # sort_values is expensive — triggers full shuffle + materialization # AVOID unless you actually need a globally ordered output: sorted_ddf = ddf.sort_values("timestamp") # use sparingly # If you need rows grouped by key (not sorted), use shuffle instead: from dask_cudf import shuffle shuffled = shuffle(ddf, on="customer_id") # redistributes by key, much cheaper ``` ## Building Distributed Collections ```python # Preferred: from_map enables column projection pushdown from dask.dataframe import from_map import cudf def load_partition(path, columns=None): return cudf.read_parquet(path, columns=columns) files = ["data/part_0000.parquet", "data/part_0001.parquet"] ddf = from_map( load_partition, files, meta=cudf.read_parquet(files[0], nrows=0), # avoids eager first-partition read ) # from_delayed works but loses projection pushdown from dask import delayed parts = [delayed(cudf.read_parquet)(f) for f in files] ddf = dask_cudf.from_delayed(parts) # fallback if from_map doesn't apply ``` ## Eager Execution Traps These calls trigger immediate computation — avoid mid-pipeline: | Call | Why it's expensive | |---|---| | `.compute()` on large collection | Pulls all data to one GPU | | `.persist()` without `client.wait()` | Silent if client not set up | | `len(ddf)` | Full scan | | `ddf.head()` / `ddf.tail()` | Materializes first/last partition | | `ddf.sort_values(...)` | Full shuffle | | `ddf.set_index(col)` | Full shuffle + sort | **Persist pattern** (when you query the same data multiple times): ```python ddf = ddf.persist() client.wait(ddf) # block until all partitions are in GPU memory result1 = ddf[ddf["a"] > 0].compute() result2 = ddf[ddf["b"] > 0].compute() # fast — data already in memory ``` **Never call `.compute()` on a collection larger than single-GPU memory** — it will OOM. Instead write to Parquet and read back in pieces. ## Writing Results ```python # Parquet (recommended — partitioned output) ddf.to_parquet("output/", write_index=False) # To single cuDF DataFrame — only when result fits in GPU memory result_cudf = ddf.compute() # To pandas — only at the very end for CPU or non-GPU handoff result_pd = ddf.to_pandas() ``` ## OOM Diagnosis ```python # Step 1: Check worker memory pressure from dashboard print(client.dashboard_link) # open in browser → Workers tab # Step 2: Increase partition count to reduce per-partition memory ddf = ddf.repartition(npartitions=ddf.npartitions * 2) # Step 3: If not already enabled, add cuDF-native spilling # (restart cluster with enable_cudf_spill=True, rmm_pool_size=0.9) # Step 4: Move filter/project before expensive operations ddf = ddf[["needed_col1", "needed_col2", "key"]] # project first ddf = ddf[ddf["amount"] > 0] # filter early result = ddf.groupby("key")["needed_col1"].sum().compute() ``` ## Anti-Patterns For new dask-cuDF code, use the backend setup shown in the Preferred API section above. The examples here focus on execution and materialization mistakes after the backend has been selected. ```python # AVOID: calling .compute() mid-pipeline intermediate = ddf.groupby("a")["b"].sum().compute() # breaks lazy graph result = intermediate.groupby("c")["b"].mean() # now CPU pandas! # CORRECT: chain lazily, compute once result = ( ddf.groupby("a")["b"].sum() .reset_index() .groupby("c")["b"].mean() .compute() ) # AVOID: collecting huge dataset to display print(ddf.compute()) # OOM risk # CORRECT: sample or head print(ddf.head(10)) # shows first 10 rows only ```
-
-
BENCHMARK.md 9 KB
# Skill Benchmark: accelerated-computing-cudf > ⚠️ **Overall verdict: INCOMPLETE — Required evidence is missing** One or more required evaluation tiers did not complete, so this benchmark is not publication-complete. ## Evaluation Metadata - Skill: `accelerated-computing-cudf` - Evaluation date: 2026-09-11 - Evaluator version: `1.5.6` - Agents: Claude Code (`aws/anthropic/bedrock-claude-opus-4-8`), Codex (`openai/openai/gpt-5.5`) - Tasks: 13 evaluation tasks (12 positive, 1 negative) - Dataset digest: `sha256:307ff81fa3f0d04ee89889dcedc5ac0208fc0eb5dba112e1c8ed515a08fa3ba9` (skill-evaluator-dataset-snapshot/1) - Attempts per task: 3 - Environment: `k8s-sandbox` - Tier 2 evidence: required for publication - Tier 3 evidence: required for publication Each task attempt ran in its own isolated sandbox pod. ## What This Report Answers The three-tier evaluation checks whether the skill: - is safe to use; - produces correct answers; - is discovered and activated when needed; - helps the agent complete the user's goal and expected workflow; and - avoids wasted skill and tool usage. ## Results at a Glance | Measure | Claude Code (Baseline → Skill Uplift) | Codex (Baseline → Skill Uplift) | |---|---:|---:| | Overall | Not available | 84.7% — baseline ran, but no comparable score was available; uplift unavailable | | Security | Not available | 76.9% → 69.2% (-7.7 points) | | Correctness | Not available | 100.0% → 100.0% (±0.0 points) | | Discoverability | Not available | 81.3% — baseline ran, but no comparable score was available; uplift unavailable | | Effectiveness | Not available | 94.4% → 90.9% (-3.5 points) | | Efficiency | Not available | 82.3% — baseline ran, but no comparable score was available; uplift unavailable | **How to read this table:** baseline is the same task attempted without the target skill. Scores are rounded to one decimal; threshold-adjacent values use additional precision so their displayed band matches the verdict. Uplift is derived from those displayed scores and shown in percentage points. Example: `47.0% → 92.0% (+45.0 points)` means the skill-assisted run scored 92.0%, 45.0 percentage points above its 47.0% no-skill baseline. A partial dimension was calculated from only the available configured signals; review the detailed report before relying on it. ## Token Usage Actual Tier 3 execution usage is reported for every observed agent/case pair and both conditions. | Agent | Dataset case | With skill | Without skill | Delta | Change | Coverage | |---|---|---:|---:|---:|---:|---| | claude-code | All cases | 14,012,571 | 10,980,314 | N/A | N/A | skill 13/14; base 13/39 | | claude-code | cudf-apply-udf__generic | 1,615,916 | 2,921,909 | -1,305,993 | -44.70% | skill 1/1; base 1/1 | | claude-code | cudf-csv-etl__generic | 640,979 | 540,152 | +100,827 | +18.67% | skill 1/1; base 1/1 | | claude-code | cudf-groupby-agg__generic | 1,334,220 | 1,114,950 | +219,270 | +19.67% | skill 1/1; base 1/1 | | claude-code | cudf-multi-join__generic | 938,388 | 609,377 | +329,011 | +53.99% | skill 1/1; base 1/1 | | claude-code | cudf-native-stream-handoff-boundary__generic | 753,844 | 676,138 | +77,706 | +11.49% | skill 1/1; base 1/1 | | claude-code | cudf-null-handling__generic | 573,498 | 698,891 | -125,393 | -17.94% | skill 1/1; base 1/1 | | claude-code | cudf-parquet-io__generic | 795,742 | 632,812 | +162,930 | +25.75% | skill 1/1; base 1/1 | | claude-code | cudf-pivot-melt__generic | 969,475 | 636,461 | +333,014 | +52.32% | skill 1/1; base 1/1 | | claude-code | cudf-string-ops__generic | 934,970 | 723,865 | +211,105 | +29.16% | skill 1/1; base 1/1 | | claude-code | cudf-timeseries-resample__generic | 585,864 | 624,629 | -38,765 | -6.21% | skill 1/1; base 1/1 | | claude-code | cudf-window-functions__generic | 2,207,481 | 675,332 | N/A | N/A | skill 1/2; base 1/1 | | claude-code | negative-deep-learning-training__generic | 458,761 | 642,097 | -183,336 | -28.55% | skill 1/1; base 1/1 | | claude-code | source-cudf-null-fillna-semantics__generic | 2,203,433 | 483,701 | +1,719,732 | +355.54% | skill 1/1; base 1/1 | | codex | All cases | 4,399,248 | 3,330,594 | +1,068,654 | +32.09% | skill 13/13; base 13/13 | | codex | cudf-apply-udf__generic | 366,287 | 333,591 | +32,696 | +9.80% | skill 1/1; base 1/1 | | codex | cudf-csv-etl__generic | 368,472 | 218,866 | +149,606 | +68.36% | skill 1/1; base 1/1 | | codex | cudf-groupby-agg__generic | 282,617 | 251,646 | +30,971 | +12.31% | skill 1/1; base 1/1 | | codex | cudf-multi-join__generic | 246,898 | 177,146 | +69,752 | +39.38% | skill 1/1; base 1/1 | | codex | cudf-native-stream-handoff-boundary__generic | 322,482 | 324,176 | -1,694 | -0.52% | skill 1/1; base 1/1 | | codex | cudf-null-handling__generic | 481,827 | 313,356 | +168,471 | +53.76% | skill 1/1; base 1/1 | | codex | cudf-parquet-io__generic | 294,404 | 206,582 | +87,822 | +42.51% | skill 1/1; base 1/1 | | codex | cudf-pivot-melt__generic | 269,590 | 280,046 | -10,456 | -3.73% | skill 1/1; base 1/1 | | codex | cudf-string-ops__generic | 258,274 | 162,821 | +95,453 | +58.62% | skill 1/1; base 1/1 | | codex | cudf-timeseries-resample__generic | 385,513 | 291,946 | +93,567 | +32.05% | skill 1/1; base 1/1 | | codex | cudf-window-functions__generic | 444,592 | 281,185 | +163,407 | +58.11% | skill 1/1; base 1/1 | | codex | negative-deep-learning-training__generic | 268,298 | 220,178 | +48,120 | +21.86% | skill 1/1; base 1/1 | | codex | source-cudf-null-fillna-semantics__generic | 409,994 | 269,055 | +140,939 | +52.38% | skill 1/1; base 1/1 | | ALL AGENTS | Dataset aggregate | 18,411,819 | 14,310,908 | N/A | N/A | skill 26/27; base 26/52 | Prompt tokens include cached reads, so total tokens are `prompt + completion` (cached is not added twice). The Efficiency score uses `(prompt - cached) + completion`. N/A means the relevant trajectory counters were not available; coverage is never estimated. ## Tier Status | Tier | Purpose | Status | Evidence | |---|---|---|---| | Tier 1 | Static validation | **PASSED WITH OBSERVATIONS** | 1 validator(s); 3 finding(s) | | Tier 2 | Semantic deduplication | **NOT RUN** | No result was recorded | | Tier 3 | Live agent evaluation | **NEUTRAL** | 2 agent(s); 13 task(s) | ## Findings and Observations <details> <summary>Show detailed findings and successful checks</summary> - **MEDIUM** SCHEMA/body_recommended_section: Missing recommended section: '## Instructions' (`skills/accelerated-computing-cudf/SKILL.md`) - **MEDIUM** SCHEMA/body_recommended_section: Missing recommended section: '## Examples' (`skills/accelerated-computing-cudf/SKILL.md`) - **LOW** SCHEMA/author_format: Author must be of the form 'Name <email@host>' (`skills/accelerated-computing-cudf/SKILL.md`) </details> ## Scoring Methodology <details> <summary>Show dimension definitions, source signals, and thresholds</summary> | Dimension | Question | Scored signals | |---|---|---| | Security | Is it safe to use? | `security` (100%) | | Correctness | Is the answer correct? | `accuracy` (100%) | | Discoverability | Was the right skill loaded when needed? | `skill_execution` (100%) | | Effectiveness | Did the skill help complete the task? | `goal_accuracy` (50%) + `behavior_check` (50%) | | Efficiency | Did it avoid wasted tool calls and token usage? | `skill_efficiency` (50%) + `token_efficiency` (50%) | - Dimension bands: PASS at 50% or above; NEUTRAL from 40% to below 50%; FAIL below 40%. - Overall Tier 3 lift: PASS at +5 points or more; FAIL at -10 points or less; values between those bands are NEUTRAL. - Overall verdict: PASS only when every configured dimension passes for at least one supported agent. Lift is reported as diagnostic evidence and does not override this gate. - The 50% attempt pass threshold is a separate per-task gate; it is not the dimension pass threshold. - Effectiveness is the equal-weight mean of goal completion (`goal_accuracy`) and expected workflow adherence (`behavior_check`). - Efficiency is 50% tool-call productivity (the backward-compatible `skill_efficiency` wire id) and 50% `token_efficiency`. Positive-case skill routing is scored under Discoverability, not Efficiency; a negative case without a routing target is N/A. N/A sources are omitted, remaining weights are renormalized, and the dimension is marked partial. Signals present in this run: - `security` (Security): unsafe operations, secret leakage, and unauthorized access. - `skill_execution` (Skill Execution): whether the expected skill was selected, decoys were avoided, and the workflow executed. - `skill_efficiency` (Tool Productivity): tool-call productivity (legacy wire id; routing is scored under Discoverability). - `accuracy` (Accuracy): final-answer correctness against the reference answer. - `goal_accuracy` (Goal Accuracy): whether the user's goal was achieved. - `behavior_check` (Behavior Check): whether the expected workflow behavior was followed. - `token_efficiency` (Token Efficiency): actual uncached prompt plus completion usage (50% of Efficiency). </details> ## Freshness Regenerate this benchmark when the skill, evaluation dataset, target agent/model, evaluator version, environment, or scoring policy changes. -
skill-card.md 4.5 KB
## Description: <br> Official NVIDIA-authored guidance for NVIDIA cuDF GPU DataFrames, pandas acceleration, dask-cuDF, ETL, joins, groupby, CSV/Parquet I/O, nullable semantics, and multi-GPU DataFrame workloads. <br> This skill is ready for commercial/non-commercial use. <br> ## Owner NVIDIA <br> ### License/Terms of Use: <br> CC-BY-4.0 AND Apache-2.0 <br> ## Use Case: <br> Developers and engineers accelerating tabular data processing with GPU DataFrames, migrating pandas code to cuDF, optimizing ETL pipelines, and scaling DataFrame workloads across multiple GPUs. <br> ### Deployment Geography for Use: <br> Global <br> ## Requirements / Dependencies: <br> **Requires API Key or External Credential:** [Not Specified] <br> **Credential Type(s):** [None identified] <br> Do not include secrets in prompts/logs/output; use least-privilege credentials; rotate keys as appropriate. <br> ## Known Risks and Mitigations: <br> Risk: Review before execution as proposals could introduce incorrect or misleading guidance into skills. <br> Mitigation: Review and scan skill before deployment. <br> ## Reference(s): <br> - [cuDF API Patterns, Gaps, and Semantic Differences](references/api-patterns.md) <br> - [cudf.pandas Accelerator Deep Dive](references/cudf-pandas-accelerator.md) <br> - [dask-cuDF Patterns](references/dask-cudf-patterns.md) <br> - [NVIDIA cuDF Documentation](https://docs.nvidia.com/cudf/) <br> - [dask-cuDF Documentation](https://docs.nvidia.com/dask-cudf/) <br> - [NVIDIA cuDF GitHub Repository](https://github.com/NVIDIA/cudf) <br> ## Skill Output: <br> **Output Type(s):** [Code, Configuration instructions, Analysis] <br> **Output Format:** [Markdown with inline Python and bash code blocks] <br> **Output Parameters:** [1D] <br> **Other Properties Related to Output:** [None] <br> ## Evaluation Agents Used: <br> - Claude Code (`aws/anthropic/bedrock-claude-opus-4-8`) <br> - Codex (`openai/openai/gpt-5.5`) <br> ## Evaluation Tasks: <br> 13 evaluation tasks (12 positive, 1 negative), each run with 3 attempts in isolated sandbox pods. <br> ## Evaluation Metrics Used: <br> Reported benchmark dimensions: <br> - Security: Checks for unsafe operations, secret leakage, and unauthorized access. <br> - Correctness: Checks final-answer correctness against the reference answer. <br> - Discoverability: Checks whether the expected skill was selected and the workflow executed. <br> - Effectiveness: Checks whether the user’s goal was achieved and expected workflow behavior was followed. <br> - Efficiency: Checks tool-call productivity and token usage efficiency. <br> Underlying evaluation signals used in this run: <br> - `security`: Detects unsafe operations, secret leakage, and unauthorized access. <br> - `accuracy`: Verifies final-answer correctness against the reference answer. <br> - `skill_execution`: Verifies whether the expected skill was selected and decoys were avoided. <br> - `goal_accuracy`: Verifies whether the user’s goal was achieved. <br> - `behavior_check`: Verifies whether the expected workflow behavior was followed. <br> - `skill_efficiency`: Measures tool-call productivity. <br> - `token_efficiency`: Measures actual uncached prompt plus completion token usage. <br> ## Evaluation Results: <br> | Measure | Claude Code (Baseline → Skill Uplift) | Codex (Baseline → Skill Uplift) | |---|---:|---:| | Overall | Not available | 84.7% — baseline ran, but no comparable score was available; uplift unavailable | | Security | Not available | 76.9% → 69.2% (-7.7 points) | | Correctness | Not available | 100.0% → 100.0% (±0.0 points) | | Discoverability | Not available | 81.3% — baseline ran, but no comparable score was available; uplift unavailable | | Effectiveness | Not available | 94.4% → 90.9% (-3.5 points) | | Efficiency | Not available | 82.3% — baseline ran, but no comparable score was available; uplift unavailable | ## Skill Version(s): <br> 4ad07b44f1 (source: git SHA, committed 2026-09-10) <br> ## Ethical Considerations: <br> NVIDIA believes Trustworthy AI is a shared responsibility and we have established policies and practices to enable development for a wide array of AI applications. When downloaded or used in accordance with our terms of service, developers should work with their internal team to ensure this skill meets requirements for the relevant industry and use case and addresses unforeseen product misuse. <br> (For Release on NVIDIA Platforms Only) <br> Please report quality, risk, security vulnerabilities or NVIDIA AI Concerns [here](https://app.intigriti.com/programs/nvidia/nvidiavdp/detail). <br> -
SKILL.md 9.2 KB
--- name: accelerated-computing-cudf description: Official NVIDIA-authored guidance for NVIDIA cuDF GPU DataFrames, pandas acceleration, dask-cuDF, ETL, joins, groupby, CSV/Parquet I/O, nullable semantics, and multi-GPU DataFrame workloads. license: CC-BY-4.0 AND Apache-2.0 metadata: author: NVIDIA tags: - cudf - dataframes - pandas - dask-cudf - etl --- # cuDF & dask-cuDF Implementer's Guide ## Compatibility - Release tracked by this skill: 26.04. - Requires NVIDIA Volta or newer on CUDA 12, or Turing or newer on CUDA 13. Release 26.04 supports CUDA 12.2-12.9 with driver 535+ or CUDA 13.0-13.1 with driver 580+, and Python 3.11-3.14. cuDF sweet spot: >100K rows. ## Naming Use NVIDIA library-first wording in user-facing answers. Keep literal RAPIDS/rapidsai URLs, package names, and release metadata when citing sources. ## Role You are a cuDF expert helping an implementer work with GPU DataFrames. The user understands pandas and their data — your job is to get them to correct, fast GPU code with minimal friction. Choose the path from the user's intent: `cudf.pandas` for broad compatibility or minimal-change acceleration, explicit cuDF for named DataFrame migrations, hot ETL paths, and parity-sensitive work. Treat source schema, row counts, null placement, ordering, and numeric tolerances as user-visible behavior. ## Critical Rules 1. **Choose the right cuDF path.** Use `cudf.pandas` for broad compatibility or minimal-change acceleration. Use explicit cuDF when the user asks to migrate DataFrame code, inspect parity, optimize a visible ETL hot path, or control unsupported operations. 2. **Size gate: 100K rows minimum.** Below that, GPU transfer overhead usually beats the speedup; use small data for correctness and benchmark larger working sets for performance. 3. **Keep conversions at boundaries.** Use `.to_pandas()`, `.values`, or `.numpy()` for display, plotting, CPU-only libraries, or final output boundaries. Keep intermediate ETL data on GPU. 4. **Float32 is your friend.** cuDF operations on float64 are slower; cast early when precision allows. 5. **Validate semantics on representative slices.** For null handling, joins, time series, reshape, or grouped logic, keep a small pandas reference path and compare shape, labels, null counts, ordering, and representative values before claiming parity. 6. **For data > GPU memory**, move to dask-cuDF with `enable_cudf_spill=True`. See `references/dask-cudf-patterns.md`. ## Three Paths to GPU DataFrames ### Path 1: cudf.pandas Accelerator (Compatibility / Minimal Change) Use when the user needs a small code change, third-party pandas compatibility, or one code path that can keep running while unsupported operations fall back. **Jupyter/IPython:** ```python %load_ext cudf.pandas import pandas as pd # now GPU-backed; falls back silently for unsupported ops ``` **Script:** ```bash python -m cudf.pandas my_script.py ``` **With multiprocessing:** ```python import cudf.pandas cudf.pandas.install() # must come BEFORE pandas import, before Pool creation from multiprocessing import Pool ``` Confirm acceleration with the cudf.pandas profiler before claiming speedup. For notebook, CLI, and stats examples, read `references/cudf-pandas-accelerator.md`. If the profile shows the hot path running on CPU, use Path 2 for explicit cuDF control. ### Path 2: Explicit cuDF API For full control, hot-path optimization, named DataFrame migrations, and parity-sensitive operations: ```python import cudf # Read data directly to GPU df = cudf.read_parquet("data.parquet") # Operations mirror pandas result = df.groupby("key")["value"].sum() merged = df.merge(lookup, on="id", how="left") filtered = df[df["amount"] > 1000] # String operations df["clean"] = df["name"].str.strip().str.lower() # To check API coverage before committing to migration: # See references/api-patterns.md for known gaps and workarounds ``` **Keep data on GPU end-to-end.** Only call `.to_pandas()` at the very end for display or CPU or non-GPU handoff. Prefer explicit cuDF for tasks involving `read_csv`/`read_parquet`, joins, groupby, reshape, nullable types, `fillna`/`where`, time buckets, rolling windows, or CPU/GPU parity checks. Add a small CPU/GPU validation path when semantics matter instead of relying on successful execution alone. For pandas code with null handling, reshape, or time-series behavior, read `references/api-patterns.md` for the relevant semantic checklist before rewriting. A `cudf.pandas` bootstrap is enough for a minimal-change request; an implementation request should make the hot path explicit and observable. For reshape-heavy pandas code (`pivot_table`, `melt`, `stack`/`unstack`, `crosstab`), keep the source schema as part of the contract: index labels, column labels or levels, `fill_value`, `aggfunc`, margins, and normalization. Use explicit cuDF where the equivalent is supported; use `cudf.pandas` or a narrow compatibility boundary when exact pandas reshape semantics matter more than rewriting every operation. Add a small pandas-reference parity check for shape, labels, and representative values before finalizing. See `references/api-patterns.md`. ### Path 3: dask-cuDF (Multi-GPU / Large Data) When dataset exceeds GPU memory. See `references/dask-cudf-patterns.md` for full patterns. ```python from dask_cuda import LocalCUDACluster from dask.distributed import Client import dask_cudf cluster = LocalCUDACluster(enable_cudf_spill=True) # one worker per GPU client = Client(cluster) ddf = dask_cudf.read_parquet("s3://bucket/data/*.parquet") result = ddf.groupby("key").agg({"value": "sum"}).compute() ``` ## Memory Management **Enable spill before OOM happens** (not after): ```python import cudf cudf.set_option("spill", True) # spill to host RAM when GPU is full ``` **RMM pool allocator** (reduces cudaMalloc overhead in pipelines with many allocations): ```python import rmm rmm.set_current_device_resource(rmm.mr.CudaAsyncMemoryResource()) # Must be called BEFORE any cuDF operations ``` | GPU Free vs Dataset | Strategy | |---|---| | Free > 2× dataset | Single GPU cuDF | | Free 1–2× dataset | cuDF + `cudf.set_option("spill", True)` | | Dataset > GPU mem | dask-cuDF | | Dataset > node mem | dask-cuDF + multi-node (see accelerated-computing-mpf) | ## Troubleshooting **No speedup vs pandas:** - Data < 100K rows? GPU overhead dominates, so treat the run as correctness validation and measure speedup on a larger working set. - Run `%%cudf.pandas.profile` — high CPU % means many fallbacks. Identify and fix those ops. - Check `references/api-patterns.md` for known gaps. **OOM (CUDA out of memory):** 1. Enable spill: `cudf.set_option("spill", True)` 2. If allocator fragmentation or repeated allocation overhead is visible, use the `accelerated-computing-rmm` memory-resource setup guidance before GPU allocations 3. Still failing: move to dask-cuDF **AttributeError / NotImplementedError:** - Check `references/api-patterns.md` for the specific operation - Keep that one operation on CPU at a narrow boundary and continue the supported pipeline on GPU - Use `.to_pandas()` only for the unsupported op, then `.from_pandas()` back **Wrong results vs pandas:** - Null/NaN handling differs: cuDF uses `<NA>` (nullable) by default, pandas uses `NaN`. See `references/api-patterns.md`. - Sort stability: cuDF sort is not guaranteed stable unless `stable=True` is passed - If the difference is due to floating point differences, try casting to higher precision floats (e.g. `float64` instead of `float32`). If the results are still different, stop. GPU and CPU algorithms will always produce different results on floating point numbers due to the non-associativity of floating point arithmetic and that cannot be fixed. ## Nullable and Fill Semantics When the user explicitly cares about pandas nullable dtypes, `fillna`, `where`/`mask`, or grouped null behavior, treat parity checks as part of the implementation. See `references/api-patterns.md` for nullable dtype examples. - Preserve nullable integer/string columns instead of filling them with sentinel values unless the source code already did that. - Keep `where`/`mask` semantics when they encode a condition. Use broad `fillna` only when the condition is exactly null-only. - Compare with `to_pandas(nullable=True)` when the pandas reference uses nullable extension dtypes. - Put the parity check in a reusable helper next to the GPU path, so future changes exercise the same nullable conversion and aggregation checks. - Validate row counts, null counts, mask truth tables, grouped aggregates, and representative dtypes before claiming semantic parity. ## Reference Files - `references/cudf-pandas-accelerator.md` — Profiling, fallback detection, cudf.pandas deep dive - `references/api-patterns.md` — Known API gaps, workarounds, semantic differences - `references/dask-cudf-patterns.md` — Multi-GPU patterns, best practices, partition tuning ## External Documentation Use WebFetch to retrieve detailed API signatures, parameter descriptions, and examples on demand. - **cuDF Documentation:** https://docs.nvidia.com/cudf/ - **dask-cuDF API Reference:** https://docs.nvidia.com/dask-cudf/ - **GitHub:** https://github.com/NVIDIA/cudf - **CHANGELOG:** https://github.com/NVIDIA/cudf/blob/main/CHANGELOG.md -
skill.oms.sig 12.2 KB · in bundle
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.