{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "apache-license-cell", "metadata": { "id": "apache-license-cell" }, "outputs": [], "source": [ "# Copyright 2025 Google LLC\n", "#\n", "# Licensed under the Apache License, Version 2.0 (the \"License\");\n", "# you may not use this file except in compliance with the License.\n", "# You may obtain a copy of the License at\n", "#\n", "# https://www.apache.org/licenses/LICENSE-2.0\n", "#\n", "# Unless required by applicable law or agreed to in writing, software\n", "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", "# See the License for the specific language governing permissions and\n", "# limitations under the License.\n" ] }, { "cell_type": "markdown", "id": "idGIs_3pWo3P", "metadata": { "id": "idGIs_3pWo3P" }, "source": [ "\n", " \n", " \n", "
\"BQ Open in BQ Studio\"Google Open in Colab Enterprise
" ] }, { "cell_type": "markdown", "id": "intro-header", "metadata": { "id": "intro-header" }, "source": [ "# Accelerated Data Science with Google Cloud and NVIDIA" ] }, { "cell_type": "markdown", "id": "authors", "metadata": { "id": "authors" }, "source": [ "| Authors |\n", "| --- |\n", "| Will Hill, Jeff Nelson |" ] }, { "cell_type": "markdown", "id": "overview", "metadata": { "id": "overview" }, "source": [ "## Overview\n", "\n", "In this notebook, you will learn how to accelerate your data science and machine learning workflows on large datasets using NVIDIA CUDA-X for data science open-source libraries with Colab Enterprise.\n", "\n", "You will build a machine learning pipeline to predict NYC taxi tip amounts. You will focus on accelerating `pandas` (using NVIDIA library `cuDF`), accelerating `scikit-learn` (using NVIDIA `cuML`), and training an XGBoost model on the GPU.\n", "\n", "And the best part is: you can get this GPU acceleration with **zero code changes** to your existing `pandas` and `scikit-learn` code." ] }, { "cell_type": "markdown", "id": "objectives", "metadata": { "id": "objectives" }, "source": [ "### Objectives\n", "\n", "* Understand how to leverage a Colab Enterprise GPU-enabled runtime.\n", "* Apply GPU acceleration to predict tip amounts using millions of records from a NYC Taxi dataset.\n", "* Accelerate `pandas` with zero code changes using NVIDIA's `cuDF` library.\n", "* Accelerate `scikit-learn` with zero code changes using NVIDIA's `cuML` library and GPUs.\n", "* Profile your code to identify and optimize performance constraints." ] }, { "cell_type": "markdown", "id": "colab-note", "metadata": { "id": "colab-note" }, "source": [ "**Note:** This notebook is intended to run in Colab Enterprise with a [GPU-enabled runtime](https://docs.cloud.google.com/colab/docs/default-runtimes-with-gpus)." ] }, { "cell_type": "markdown", "id": "services-costs", "metadata": { "id": "services-costs" }, "source": [ "### Services and Costs\n", "\n", "This tutorial uses the following billable components of Google Cloud:\n", "\n", "* **Colab Enterprise**: [Pricing](https://cloud.google.com/colab/pricing)\n", "* **Google Cloud Storage**: [Pricing](https://cloud.google.com/storage/pricing)\n", "\n", "You can use the [Pricing Calculator](https://cloud.google.com/products/calculator) to generate a cost estimate based on your projected usage." ] }, { "cell_type": "markdown", "id": "setup-section", "metadata": { "id": "setup-section" }, "source": [ "## Setup\n", "\n", "**Note:** This notebook is intended to run in Colab Enterprise with a GPU-enabled runtime (e.g. T4, L4, A100, etc).\n", "\n", "Let's start by verifying that a GPU is available in our runtime." ] }, { "cell_type": "markdown", "id": "RVFfkjtQWo3R", "metadata": { "id": "RVFfkjtQWo3R" }, "source": [ "### Configure GPU and confirm availability\n", "\n", "You can confirm the GPU is recognized by running the `nvidia-smi` command. It displays the driver version and GPU details (such as the NVIDIA L4)." ] }, { "cell_type": "code", "execution_count": null, "id": "m4HeJa7Wbpd9", "metadata": { "id": "m4HeJa7Wbpd9" }, "outputs": [], "source": [ "!nvidia-smi" ] }, { "cell_type": "markdown", "id": "dataset-intro", "metadata": { "id": "dataset-intro" }, "source": [ "---\n", "\n", "## Prepare the NYC taxi dataset\n", "\n", "This tutorial uses the [NYC Taxi & Limousine Commission (TLC) Trip Record Data](https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page).\n", "\n", "The dataset contains individual trip records from yellow taxis in New York City, and includes fields like:\n", "* Pick-up and drop-off dates, times, and locations\n", "* Trip distances\n", "* Itemized fare amounts\n", "* Passenger counts\n", "* **Tip amounts** (*the target variable we will predict*)" ] }, { "cell_type": "markdown", "id": "download-data-markdown", "metadata": { "id": "download-data-markdown" }, "source": [ "### Download the data\n", "\n", "Next, download the trip data for all of 2024. The data is stored in the efficient, columnar Parquet file format.\n", "\n", "**Note:** We download the entire year, but the notebook only loads the first month's data to keep execution times fast. Feel free to experiment with the larger dataset later." ] }, { "cell_type": "code", "execution_count": null, "id": "download-data-code", "metadata": { "id": "download-data-code" }, "outputs": [], "source": [ "from tqdm import tqdm\n", "import requests\n", "import time\n", "import os\n", "\n", "YEAR = 2024\n", "DATA_DIR = \"nyc_taxi_data\"\n", "\n", "os.makedirs(DATA_DIR, exist_ok=True)\n", "print(f\"Checking/Downloading files for {YEAR}...\")\n", "\n", "for month in tqdm(range(1, 13), unit=\"file\"):\n", " file_name = f\"yellow_tripdata_{YEAR}-{month:02d}.parquet\"\n", " local_path = os.path.join(DATA_DIR, file_name)\n", " url = f\"https://d37ci6vzurychx.cloudfront.net/trip-data/{file_name}\"\n", "\n", " if not os.path.exists(local_path):\n", " try:\n", " with requests.get(url, stream=True) as response:\n", " response.raise_for_status()\n", " with open(local_path, 'wb') as f:\n", " for chunk in response.iter_content(chunk_size=8192):\n", " f.write(chunk)\n", " time.sleep(1)\n", " except requests.exceptions.HTTPError as e:\n", " print(f\"\\nSkipping {file_name}: {e}\")\n", " if os.path.exists(local_path):\n", " os.remove(local_path)\n", "\n", "print(\"\\nDownload complete.\")" ] }, { "cell_type": "markdown", "id": "cudf-pandas-intro", "metadata": { "id": "cudf-pandas-intro" }, "source": [ "---\n", "\n", "## Accelerate pandas with NVIDIA cuDF\n", "\n", "[NVIDIA CUDA for DataFrames (cuDF)](https://docs.rapids.ai/api/cudf/stable/) is an open-source, GPU-accelerated library that enables fast DataFrame operations.\n", "\n", "The `pandas` library runs on the CPU and can be slow with large datasets. The NVIDIA `%load_ext cudf.pandas` magic command dynamically patches pandas to use GPU acceleration, falling back to the CPU if needed.\n", "\n", "We use this magic command instead of a standard import because it provides 'zero code change' acceleration. You don't have to rewrite any of your existing code. A similar command, `%load_ext cuml.accel`, does the exact same thing for `scikit-learn models`!" ] }, { "cell_type": "code", "execution_count": null, "id": "enable-cudf-pandas", "metadata": { "id": "enable-cudf-pandas" }, "outputs": [], "source": [ "%load_ext cudf.pandas\n", "import pandas as pd\n", "\n", "pd # Note the output indicates cudf.pandas is active (ModuleAccelerator)" ] }, { "cell_type": "markdown", "id": "restart-note-cudf", "metadata": { "id": "restart-note-cudf" }, "source": [ "**Note:** To ensure a clean environment, you may need to restart your kernel when switching between standard pandas and `cudf.pandas`." ] }, { "cell_type": "markdown", "id": "load-clean-data", "metadata": { "id": "load-clean-data" }, "source": [ "### Load and Clean Data\n", "\n", "Let's load the data we downloaded and clean out invalid trips (e.g. negative fares or distances, non-credit card payments where tips aren't tracked). Because `cudf.pandas` is active, this data loading and filtering happens on the GPU." ] }, { "cell_type": "code", "execution_count": null, "id": "load-clean-data-code", "metadata": { "id": "load-clean-data-code" }, "outputs": [], "source": [ "import time\n", "import glob\n", "\n", "# Start a timer so we can track performance\n", "start_time = time.perf_counter()\n", "\n", "# 1. Load data\n", "df = pd.concat(\n", " [pd.read_parquet(f) for f in glob.glob(\"nyc_taxi_data/*-01.parquet\")],\n", " ignore_index=True\n", ")\n", "print(f\"Loaded {len(df):,} records.\")\n", "\n", "# 2. Clean data\n", "df = df[\n", " (df['fare_amount'] > 0) & (df['fare_amount'] < 500) &\n", " (df['trip_distance'] > 0) & (df['trip_distance'] < 100) &\n", " (df['tip_amount'] >= 0) & (df['tip_amount'] < 100) &\n", " (df['payment_type'] == 1) # Credit card only (tips recorded)\n", "].copy()\n", "\n", "\n", "# 3. Downcast numeric columns to save memory\n", "float_cols = df.select_dtypes(include=['float64']).columns\n", "df[float_cols] = df[float_cols].astype('float32')\n", "int_cols = df.select_dtypes(include=['int64']).columns\n", "df[int_cols] = df[int_cols].astype('int32')\n", "\n", "print(f\"Clean records remaining: {len(df):,}.\")\n", "\n", "import gc\n", "gc.collect()\n", "print(f\"Load and Clean Time: {time.perf_counter() - start_time:.2f} seconds\")" ] }, { "cell_type": "markdown", "id": "feature-engineering", "metadata": { "id": "feature-engineering" }, "source": [ "### Feature Engineering\n", "\n", "Next, we prepare our features for the machine learning models. We will extract time-based features, log-transform the fare, and create route-specific aggregations based on the pickup and dropoff location IDs.\n", "\n", "Each of these vectorized operations are accelerated by the GPU." ] }, { "cell_type": "code", "execution_count": null, "id": "feature-engineering-code", "metadata": { "id": "feature-engineering-code" }, "outputs": [], "source": [ "import numpy as np\n", "\n", "start_time = time.perf_counter()\n", "\n", "# Time Features\n", "df['hour'] = df['tpep_pickup_datetime'].dt.hour\n", "df['dow'] = df['tpep_pickup_datetime'].dt.dayofweek\n", "df['is_weekend'] = (df['dow'] >= 5).astype(int)\n", "df['is_rush_hour'] = (\n", " ((df['hour'] >= 7) & (df['hour'] <= 9)) |\n", " ((df['hour'] >= 17) & (df['hour'] <= 19))\n", ").astype(int)\n", "\n", "# Fare Amount Features\n", "df['fare_log'] = np.log1p(df['fare_amount'])\n", "df['fare_decimal'] = (df['fare_amount'] % 1 * 100).astype(int)\n", "df['is_round_fare'] = (df['fare_amount'] % 5 == 0).astype(int)\n", "\n", "# Route Features\n", "df['route_id'] = df['PULocationID'].astype(str) + '_' + df['DOLocationID'].astype(str)\n", "route_counts = df['route_id'].value_counts()\n", "df['route_frequency'] = df['route_id'].map(route_counts)\n", "\n", "# Location Aggregation Features (Mean and Std Tip by Pickup Location)\n", "pu_tip_stats = df.groupby('PULocationID')['tip_amount'].agg(['mean', 'std']).reset_index()\n", "pu_tip_stats.columns = ['PULocationID', 'pu_tip_mean', 'pu_tip_std']\n", "df = df.merge(pu_tip_stats, on='PULocationID', how='left')\n", "\n", "print(f\"Feature Engineering Time: {time.perf_counter() - start_time:.2f} seconds\")" ] }, { "cell_type": "markdown", "id": "cuml-intro", "metadata": { "id": "cuml-intro" }, "source": [ "---\n", "\n", "## Accelerate scikit-learn with NVIDIA cuML\n", "\n", "Similar to how we accelerated `pandas`, NVIDIA provides CUDA-X for Machine Learing or cuML to accelerate `scikit-learn` functions (like Linear Regression, Random Forest, K-Means, PCA, etc.) dynamically.\n", "\n", "Load `cuml.accel` to activate zero-code-change GPU-acceleration for `scikit-learn`.\n", "\n", "*Note: The code below includes a brief compatibility fix to restore a missing utility in newer IPython versions required for the extension to load correctly.*" ] }, { "cell_type": "code", "execution_count": null, "id": "enable-cuml", "metadata": { "id": "enable-cuml" }, "outputs": [], "source": [ "import IPython.core.magic\n", "if not hasattr(IPython.core.magic, 'output_can_be_silenced'):\n", " IPython.core.magic.output_can_be_silenced = lambda x: x\n", "\n", "%load_ext cuml.accel\n", "import sys\n", "print(\"cuml.accel active:\", 'cuml.accel' in sys.modules)" ] }, { "cell_type": "markdown", "id": "prepare-xy-markdown", "metadata": { "id": "prepare-xy-markdown" }, "source": [ "### Prepare Features and Target\n", "\n", "Let's select our feature columns (`x`) and define our target variable `tip_amount` (`y`). We will also set up a K-Fold cross-validator." ] }, { "cell_type": "code", "execution_count": null, "id": "prepare-xy-code", "metadata": { "id": "prepare-xy-code" }, "outputs": [], "source": [ "from sklearn.model_selection import KFold\n", "\n", "feature_cols = [\n", " 'trip_distance', 'fare_amount', 'passenger_count',\n", " 'hour', 'dow', 'is_weekend', 'is_rush_hour',\n", " 'fare_log', 'fare_decimal', 'is_round_fare',\n", " 'route_frequency', 'pu_tip_mean', 'pu_tip_std',\n", " 'PULocationID', 'DOLocationID'\n", "]\n", "\n", "X = df[feature_cols].fillna(df[feature_cols].median())\n", "y = df['tip_amount']\n", "\n", "n_splits = 3\n", "kf = KFold(n_splits=n_splits, shuffle=True, random_state=42)" ] }, { "cell_type": "markdown", "id": "train-models", "metadata": { "id": "train-models" }, "source": [ "---\n", "\n", "## Train Individual Models with Cross-Validation\n", "\n", "Now we will train three different predictive models to forecast tip amounts. We will use the standard `scikit-learn` API and NVIDIA `XGBoost`.\n", "\n", "Because we loaded `cuml.accel`, some `scikit-learn` functions will execute on the GPU. For XGBoost to run on the GPU, we simply set `device='cuda'`." ] }, { "cell_type": "markdown", "id": "xgb-markdown", "metadata": { "id": "xgb-markdown" }, "source": [ "### 1. XGBoost\n", "\n", "XGBoost natively supports GPU backends. By configuring the `device` parameter to `\"cuda\"`, XGBoost leverages the GPU to train gradient-boosted trees significantly faster." ] }, { "cell_type": "code", "execution_count": null, "id": "xgb-code", "metadata": { "id": "xgb-code" }, "outputs": [], "source": [ "from tqdm.auto import tqdm\n", "import xgboost as xgb\n", "from sklearn.metrics import mean_squared_error\n", "\n", "start_time = time.perf_counter()\n", "\n", "def train_xgb_cv(X, y):\n", " params = {\n", " 'objective': 'reg:squarederror',\n", " 'eval_metric': 'rmse',\n", " 'max_depth': 5,\n", " 'learning_rate': 0.1,\n", " 'device': 'cuda', # <--- Enable GPU Acceleration\n", " 'verbosity': 0,\n", " 'tree_method': 'hist', # Explicit histogram method\n", " 'max_bin': 256, # Faster histogram binning\n", " 'subsample': 0.8,\n", " 'colsample_bytree': 0.8,\n", " 'sampling_method': 'gradient_based' # GPU-optimized sampling\n", " }\n", "\n", " rmses = []\n", " preds_all = np.zeros(len(y))\n", "\n", " for train_idx, val_idx in tqdm(kf.split(X), total=n_splits):\n", " X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]\n", " y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]\n", "\n", " dtrain = xgb.DMatrix(X_train, label=y_train)\n", " dval = xgb.DMatrix(X_val, label=y_val)\n", "\n", " model = xgb.train(params,\n", " dtrain,\n", " num_boost_round=1000,\n", " evals=[(dval, 'val')],\n", " early_stopping_rounds=50,\n", " verbose_eval=False)\n", "\n", " preds = model.predict(dval)\n", " preds_all[val_idx] = preds\n", " rmses.append(np.sqrt(mean_squared_error(y_val, preds)))\n", "\n", " return np.mean(rmses), preds_all\n", "\n", "xgb_rmse, xgb_preds = train_xgb_cv(X, y)\n", "print(f\"\\n{'XGBoost RMSE:':<20} ${xgb_rmse:.4f}\")\n", "print(f\"{'Time:':<20} {time.perf_counter() - start_time:.2f} seconds\")" ] }, { "cell_type": "markdown", "id": "linear-model-markdown", "metadata": { "id": "linear-model-markdown" }, "source": [ "### 2. Linear Regression\n", "\n", "Here we use the standard `sklearn.linear_model.LinearRegression` class. We also use `StandardScaler` to scale the features. Behind the scenes, `cuml.accel` intercepts these calls and executes them on the GPU using `cuML`." ] }, { "cell_type": "code", "execution_count": null, "id": "linear-model-code", "metadata": { "id": "linear-model-code" }, "outputs": [], "source": [ "from sklearn.linear_model import LinearRegression\n", "from sklearn.preprocessing import StandardScaler\n", "\n", "start_time = time.perf_counter()\n", "\n", "def train_linreg_cv(X, y):\n", " rmses = []\n", " preds_all = np.zeros(len(y))\n", " for train_idx, val_idx in tqdm(kf.split(X), total=n_splits):\n", " X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]\n", " y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]\n", "\n", " # Scale features\n", " scaler = StandardScaler()\n", " X_train_scaled = scaler.fit_transform(X_train)\n", " X_val_scaled = scaler.transform(X_val)\n", "\n", " # Automatically accelerated by cuML\n", " model = LinearRegression()\n", " model.fit(X_train_scaled, y_train)\n", "\n", " preds = model.predict(X_val_scaled)\n", " preds_all[val_idx] = preds\n", " rmses.append(np.sqrt(mean_squared_error(y_val, preds)))\n", "\n", " return np.mean(rmses), preds_all\n", "\n", "linreg_rmse, linreg_preds = train_linreg_cv(X, y)\n", "print(f\"\\n{'Linear Reg RMSE:':<20} ${linreg_rmse:.4f}\")\n", "print(f\"{'Time:':<20} {time.perf_counter() - start_time:.2f} seconds\")" ] }, { "cell_type": "markdown", "id": "rf-markdown", "metadata": { "id": "rf-markdown" }, "source": [ "### 3. Random Forest\n", "\n", "We can also use a complex ensemble model like Random Forest. Tree-based models are traditionally slow to train on the CPU, but with GPU acceleration, millions of rows can be processed much quicker." ] }, { "cell_type": "code", "execution_count": null, "id": "rf-code", "metadata": { "id": "rf-code" }, "outputs": [], "source": [ "from sklearn.ensemble import RandomForestRegressor\n", "\n", "start_time = time.perf_counter()\n", "\n", "def train_rf_cv(X, y):\n", " rmses = []\n", " preds_all = np.zeros(len(y))\n", " for train_idx, val_idx in tqdm(kf.split(X), total=n_splits):\n", " X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]\n", " y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]\n", "\n", " # Automatically accelerated by cuML\n", " model = RandomForestRegressor(\n", " n_estimators=100,\n", " max_depth=10,\n", " n_jobs=-1,\n", " max_features='sqrt',\n", " random_state=42\n", " )\n", " model.fit(X_train, y_train)\n", "\n", " preds = model.predict(X_val)\n", " preds_all[val_idx] = preds\n", " rmses.append(np.sqrt(mean_squared_error(y_val, preds)))\n", "\n", " return np.mean(rmses), preds_all\n", "\n", "rf_rmse, rf_preds = train_rf_cv(X, y)\n", "print(f\"\\n{'Random Forest RMSE:':<20} ${rf_rmse:.4f}\")\n", "print(f\"{'Time:':<20} {time.perf_counter() - start_time:.2f} seconds\")" ] }, { "cell_type": "markdown", "id": "ensemble-eval-markdown", "metadata": { "id": "ensemble-eval-markdown" }, "source": [ "---\n", "\n", "## Evaluate the End-to-End Pipeline\n", "\n", "Finally, let's combine the predictions from our three models using a simple linear ensemble. This often provides a slight accuracy lift." ] }, { "cell_type": "markdown", "id": "lRymLxwvWo3S", "metadata": { "id": "lRymLxwvWo3S" }, "source": [ "### Ensemble Predictions" ] }, { "cell_type": "code", "execution_count": null, "id": "ensemble-eval-code", "metadata": { "id": "ensemble-eval-code" }, "outputs": [], "source": [ "# Fit a linear regression on the predictions to find optimal weights\n", "ensemble_weights = LinearRegression(positive=True, fit_intercept=False).fit(\n", " np.c_[xgb_preds, rf_preds, linreg_preds], y\n", ").coef_\n", "\n", "# Normalize weights\n", "ensemble_weights = ensemble_weights / ensemble_weights.sum()\n", "\n", "ensemble_preds = np.c_[xgb_preds, rf_preds, linreg_preds] @ ensemble_weights\n", "ensemble_rmse = np.sqrt(mean_squared_error(y, ensemble_preds))\n", "\n", "print(f\"\\n{'Model':<20} {'RMSE':>10}\")\n", "print(\"-\" * 32)\n", "print(f\"{'Linear Regression':<20} ${linreg_rmse:>9.4f}\")\n", "print(f\"{'Random Forest':<20} ${rf_rmse:>9.4f}\")\n", "print(f\"{'XGBoost':<20} ${xgb_rmse:>9.4f}\")\n", "print(\"-\" * 32)\n", "print(f\"{'Ensemble':<20} ${ensemble_rmse:>9.4f}\")\n", "print(f\"\\nEnsemble lift: ${xgb_rmse - ensemble_rmse:.4f}\")" ] }, { "cell_type": "markdown", "id": "compare-cpu-gpu-perf-md", "metadata": { "id": "compare-cpu-gpu-perf-md" }, "source": [ "---\n", "\n", "## Compare CPU vs. GPU performance\n", "\n", "To properly measure the performance difference, you'll first run an end-to-end data science pipeline on the CPU, and then run it again on the GPU.\n", "\n", "**First, restart the kernel** to ensure you start with a clean execution state." ] }, { "cell_type": "code", "execution_count": null, "id": "restart-kernel-cell", "metadata": { "id": "restart-kernel-cell" }, "outputs": [], "source": [ "import IPython\n", "IPython.Application.instance().kernel.do_shutdown(True)" ] }, { "cell_type": "markdown", "id": "pipeline-def-md", "metadata": { "id": "pipeline-def-md" }, "source": [ "### Define the data science pipeline\n", "\n", "Next, wrap the core workflow (loading data, cleaning, feature engineering, and model training) into a single function. This function allows us to pass in standard `pandas` or GPU-accelerated `cudf.pandas`, and toggle the XGBoost `device`.\n", "\n", "**Note:** For a clear CPU vs. GPU comparison, this function uses fewer features and skips cross-validation to focus on core pipeline steps." ] }, { "cell_type": "code", "execution_count": null, "id": "iuJhAisN6xa5", "metadata": { "id": "iuJhAisN6xa5" }, "outputs": [], "source": [ "def run_ml_pipeline(pd_module, use_gpu=False):\n", " import time\n", " import glob\n", " import numpy as np\n", " from sklearn.ensemble import RandomForestRegressor\n", " import xgboost as xgb\n", "\n", " timings = {}\n", "\n", " # 1. Load Data\n", " t0 = time.perf_counter()\n", " df = pd_module.concat(\n", " [pd_module.read_parquet(f) for f in glob.glob(\"nyc_taxi_data/*-01.parquet\")],\n", " ignore_index=True\n", " )\n", " timings['Load Data'] = time.perf_counter() - t0\n", "\n", " # 2. Clean Data\n", " t0 = time.perf_counter()\n", " df = df[\n", " (df['fare_amount'] > 0) & (df['fare_amount'] < 500) &\n", " (df['trip_distance'] > 0) & (df['trip_distance'] < 100) &\n", " (df['tip_amount'] >= 0) & (df['tip_amount'] < 100) &\n", " (df['payment_type'] == 1)\n", " ].copy()\n", "\n", " # Downcast numeric columns to save memory\n", " float_cols = df.select_dtypes(include=['float64']).columns\n", " df[float_cols] = df[float_cols].astype('float32')\n", " int_cols = df.select_dtypes(include=['int64']).columns\n", " df[int_cols] = df[int_cols].astype('int32')\n", " timings['Clean Data'] = time.perf_counter() - t0\n", "\n", " # 3. Feature Engineering\n", " t0 = time.perf_counter()\n", " df['hour'] = df['tpep_pickup_datetime'].dt.hour\n", " df['dow'] = df['tpep_pickup_datetime'].dt.dayofweek\n", " df['is_weekend'] = (df['dow'] >= 5).astype(int)\n", " df['fare_log'] = np.log1p(df['fare_amount'])\n", " timings['Feature Engineering'] = time.perf_counter() - t0\n", "\n", " # 4. Modeling Prep\n", " feature_cols = ['trip_distance', 'fare_amount', 'passenger_count', 'hour', 'dow', 'is_weekend', 'fare_log']\n", " X = df[feature_cols].fillna(df[feature_cols].median())\n", " y = df['tip_amount'].copy()\n", "\n", " # Free memory we no longer need\n", " del df\n", " import gc\n", " gc.collect()\n", "\n", " # 5. Train Random Forest\n", " t0 = time.perf_counter()\n", " rf_model = RandomForestRegressor(\n", " n_estimators=100,\n", " max_depth=10,\n", " n_jobs=-1,\n", " max_features='sqrt',\n", " random_state=42\n", " ).fit(X, y)\n", " timings['Train Random Forest'] = time.perf_counter() - t0\n", "\n", " # 6. Train XGBoost\n", " t0 = time.perf_counter()\n", " params = {\n", " 'objective': 'reg:squarederror',\n", " 'max_depth': 5,\n", " 'n_estimators': 100,\n", " 'random_state': 42\n", " }\n", " if use_gpu:\n", " params['device'] = 'cuda'\n", " params['tree_method'] = 'hist'\n", " xgb_model = xgb.XGBRegressor(**params).fit(X, y)\n", " timings['Train XGBoost'] = time.perf_counter() - t0\n", "\n", " del X\n", " del y\n", " gc.collect()\n", "\n", " return timings" ] }, { "cell_type": "markdown", "id": "run-cpu-md", "metadata": { "id": "run-cpu-md" }, "source": [ "### Run on your CPU\n", "\n", "Call the pipeline using standard `pandas` and `scikit-learn` (the CPU defaults)." ] }, { "cell_type": "code", "execution_count": null, "id": "run-cpu-cell", "metadata": { "id": "run-cpu-cell" }, "outputs": [], "source": [ "import pandas as pd\n", "\n", "print(\"Running pipeline on CPU...\")\n", "cpu_times = run_ml_pipeline(pd, use_gpu=False)\n", "print(\"CPU Execution Finished.\")" ] }, { "cell_type": "markdown", "id": "run-gpu-md", "metadata": { "id": "run-gpu-md" }, "source": [ "### Run on your GPU\n", "\n", "Now, load the NVIDIA library extensions. You will pass the accelerated `cudf.pandas` module into the pipeline, and set your XGBoost device to `cuda`." ] }, { "cell_type": "code", "execution_count": null, "id": "run-gpu-cell", "metadata": { "id": "run-gpu-cell" }, "outputs": [], "source": [ "import IPython.core.magic\n", "if not hasattr(IPython.core.magic, 'output_can_be_silenced'):\n", " IPython.core.magic.output_can_be_silenced = lambda x: x\n", "\n", "%load_ext cudf.pandas\n", "%load_ext cuml.accel\n", "import pandas as pd\n", "\n", "print(\"Running pipeline on GPU...\")\n", "gpu_times = run_ml_pipeline(pd, use_gpu=True)\n", "print(\"GPU Execution Finished.\")" ] }, { "cell_type": "markdown", "id": "chart-gpu-perf-md", "metadata": { "id": "chart-gpu-perf-md" }, "source": [ "### Visualize the performance speedup\n", "\n", "With both benchmarks complete, you can visualize the execution timings side-by-side using `matplotlib`." ] }, { "cell_type": "code", "execution_count": null, "id": "chart-gpu-perf-cell", "metadata": { "id": "chart-gpu-perf-cell" }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import numpy as np\n", "\n", "labels = list(cpu_times.keys())\n", "cpu_values = list(cpu_times.values())\n", "gpu_values = list(gpu_times.values())\n", "\n", "x = np.arange(len(labels))\n", "width = 0.35\n", "\n", "fig, ax = plt.subplots(figsize=(10, 6))\n", "rects1 = ax.bar(x - width/2, cpu_values, width, label='CPU', color='#4285F4')\n", "rects2 = ax.bar(x + width/2, gpu_values, width, label='GPU', color='#76B900')\n", "\n", "ax.set_ylabel('Execution Time (seconds)')\n", "ax.set_title('NYC Taxi ML Pipeline: CPU vs. GPU Performance')\n", "ax.set_xticks(x)\n", "ax.set_xticklabels(labels, rotation=45, ha=\"right\")\n", "ax.legend()\n", "\n", "# Add data labels\n", "def autolabel(rects):\n", " for rect in rects:\n", " height = rect.get_height()\n", " ax.annotate(f'{height:.2f}s',\n", " xy=(rect.get_x() + rect.get_width() / 2, height),\n", " xytext=(0, 3), # 3 points vertical offset\n", " textcoords=\"offset points\",\n", " ha='center', va='bottom', fontsize=9)\n", "autolabel(rects1)\n", "autolabel(rects2)\n", "\n", "plt.tight_layout()\n", "plt.show()\n", "\n", "# Calculate overall speedup\n", "total_cpu_time = sum(cpu_values)\n", "total_gpu_time = sum(gpu_values)\n", "overall_speedup = total_cpu_time / total_gpu_time\n", "print(f\"\\nOverall Pipeline Speedup: {overall_speedup:.2f}x faster on GPU!\")" ] }, { "cell_type": "markdown", "id": "profiling-tutorial-md", "metadata": { "id": "profiling-tutorial-md" }, "source": [ "---\n", "\n", "## Profile your code to find bottlenecks\n", "\n", "When using `cudf.pandas`, most functions run on the GPU. But if a specific operation is not yet supported by cuDF, the execution temporarily falls back to the CPU. Identifying these fallbacks is crucial for achieving maximum performance.\n", "\n", "NVIDIA provides two built-in Jupyter magic commands for this purpose: `%%cudf.pandas.profile` and `%%cudf.pandas.line_profile`." ] }, { "cell_type": "markdown", "id": "profiling-profile-md", "metadata": { "id": "profiling-profile-md" }, "source": [ "### High-level profiling with `%%cudf.pandas.profile`\n", "\n", "The `%%cudf.pandas.profile` magic command provides a detailed summary of your code block, showing exactly which pandas functions were called, how many times they ran, and whether they executed on the GPU or fell back to the CPU." ] }, { "cell_type": "code", "execution_count": null, "id": "profiling-profile-cell", "metadata": { "id": "profiling-profile-cell" }, "outputs": [], "source": [ "%%cudf.pandas.profile\n", "\n", "import glob\n", "import pandas as pd\n", "\n", "df = pd.concat([pd.read_parquet(f) for f in glob.glob(\"nyc_taxi_data/*-01.parquet\")], ignore_index=True)\n", "\n", "summary = (\n", " df\n", " .groupby(['PULocationID', 'payment_type'])\n", " [['passenger_count', 'fare_amount', 'tip_amount']]\n", " .agg(['min', 'mean', 'max'])\n", ")" ] }, { "cell_type": "markdown", "id": "profiling-line-md", "metadata": { "id": "profiling-line-md" }, "source": [ "### Line-by-line profiling with `%%cudf.pandas.line_profile`\n", "\n", "To find exactly where CPU execution is slowing you down, you can use the more granular `%%cudf.pandas.line_profile`.\n", "\n", "Below, we write an intentionally inefficient operation (using `df.apply` to categorize an hour of the day) and view the line-by-line profile.\n", "\n", "**Note:** the \"total time\" includes a one-time warmup cost for just-in-time completion, while the \"line stats\" reveal super fast execution once the code is compiled." ] }, { "cell_type": "code", "execution_count": null, "id": "48e752b2-7b9d-420a-a6bd-7f613d20dbcc", "metadata": { "id": "48e752b2-7b9d-420a-a6bd-7f613d20dbcc" }, "outputs": [], "source": [ "%%cudf.pandas.line_profile\n", "\n", "import glob\n", "import pandas as pd\n", "\n", "df = pd.concat([pd.read_parquet(f) for f in glob.glob(\"nyc_taxi_data/*-01.parquet\")], ignore_index=True)\n", "df = df.sample(1_000)\n", "\n", "# Iterating row-by-row or using custom python apply functions often falls back to the CPU\n", "def categorize_hour(hour):\n", " if hour < 12:\n", " return 'Morning'\n", " else:\n", " return 'Afternoon/Evening'\n", "\n", "df['hour'] = df['tpep_pickup_datetime'].dt.hour\n", "\n", "df['time_of_day_slow'] = df['hour'].apply(categorize_hour)\n", "\n", "# Using vectorized pandas operations (like pd.cut) stays entirely on the GPU\n", "cut_bins = [-1, 11, 24]\n", "cut_labels = ['Morning', 'Afternoon/Evening']\n", "df['time_of_day_fast'] = pd.cut(df['hour'], bins=cut_bins, labels=cut_labels)" ] }, { "cell_type": "markdown", "id": "cleanup-md", "metadata": { "id": "cleanup-md" }, "source": [ "---\n", "\n", "## Clean Up\n", "\n", "To avoid incurring unexpected charges to your Google Cloud account, you need to clean up the resources you created." ] }, { "cell_type": "markdown", "id": "delete-resources-md-id", "metadata": { "id": "delete-resources-md-id" }, "source": [ "### Delete resources\n", "\n", "The following code permanently deletes the locally downloaded NYC taxi dataset." ] }, { "cell_type": "code", "execution_count": null, "id": "delete-resources-cell", "metadata": { "id": "delete-resources-cell" }, "outputs": [], "source": [ "# Remove NYC taxi dataset on the Colab runtime\n", "print(\"Deleting local 'nyc_taxi_data' directory...\")\n", "!rm -rf nyc_taxi_data\n", "print(\"Local files deleted.\")" ] }, { "cell_type": "markdown", "id": "shutdown-colab-md", "metadata": { "id": "shutdown-colab-md" }, "source": [ "### Shut down your Colab runtime\n", "\n", "* In the Google Cloud console, go to the Colab Enterprise **Runtimes** page.\n", "* In the **Region** menu, select the region that contains your runtime.\n", "* Select the runtime you want to delete.\n", "* Click **Delete**.\n", "* Click **Confirm**." ] }, { "cell_type": "markdown", "id": "delete-notebook-md", "metadata": { "id": "delete-notebook-md" }, "source": [ "### Delete your Notebook\n", "\n", "* In the Google Cloud console, go to the Colab Enterprise **My Notebooks** page.\n", "* In the **Region** menu, select the region that contains your notebook.\n", "* Select the notebook you want to delete.\n", "* Click **Delete**.\n", "* Click **Confirm**." ] }, { "cell_type": "markdown", "id": "recap-md", "metadata": { "id": "recap-md" }, "source": [ "---\n", "\n", "## Recap\n", "\n", "Congratulations! You've successfully accelerated a pandas and scikit-learn machine learning workflow using NVIDIA cuDF and cuML on Colab Enterprise. By simply adding a few magic commands (`%load_ext cudf.pandas` and `%load_ext cuml.accel`), your standard `pandas` and `scikit-learn` code runs on the GPU, processing millions of rows and fitting complex models in seconds instead of minutes.\n", "\n", "### Reference docs\n", "\n", "* [Colab Enterprise Documentation](https://cloud.google.com/colab/docs/introduction)\n", "* [NVIDIA cuDF Documentation](https://docs.rapids.ai/api/cudf/stable/)\n", "* [NVIDIA cuML Documentation](https://docs.rapids.ai/api/cuml/stable/)" ] } ], "metadata": { "accelerator": "GPU", "colab": { "gpuType": "L4", "name": "gpu_accelerated_regression.ipynb", "provenance": [] }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.3" } }, "nbformat": 4, "nbformat_minor": 5 }