What you will be able to do
- Train a model in a Python stored procedure on a Snowpark-optimized warehouse
- Keep a hold-out test split separate from cross validation and tuning
- Configure a hyperparameter search with a metric and a maximize/minimize mode
- Down-sample a table with SAMPLE, and say what the sources leave out about up-sampling
- Choose between per-partition training in a UDTF, Many Model Training, and an external function
1.Training with Python stored procedures
The most direct way to train a model in Snowflake is a Python stored procedure. Inside it, nested Snowpark queries load and transform the dataset. The data is then pulled into the procedure's memory for preprocessing and fitting, and the fitted model is written to a stage. Training can need a lot of memory, so Snowflake provides Snowpark-optimized warehouses for running single-node training with custom code.
Checkpoint 1 of 9· Fill the gap
Which warehouse type makes this the right home for a memory-heavy training procedure?
CREATE OR REPLACE WAREHOUSE snowpark_opt_wh WITH
WAREHOUSE_SIZE = 'MEDIUM'
WAREHOUSE_TYPE = ' ? '
MAX_CONCURRENCY_LEVEL = 1;Snowpark-optimized is the documented warehouse type for single-node ML training in stored procedures. MEDIUM gives exactly one Snowpark-optimized node.
Source: docs.snowflake.comThe guidelines are specific. Set WAREHOUSE_SIZE = MEDIUM so the warehouse has one Snowpark-optimized node. Make it multi-cluster if you need concurrency. Keep other workloads off it. The procedure's nested queries can run on a separate warehouse, chosen with session.use_warehouse(), which you can size independently to match the data. The procedure itself declares its libraries in PACKAGES:
CREATE OR REPLACE PROCEDURE train()
RETURNS VARIANT
LANGUAGE PYTHON
RUNTIME_VERSION = 3.12
PACKAGES = ('snowflake-snowpark-python', 'scikit-learn', 'joblib')
HANDLER = 'main'
AS $$Sources1
2.Partitioning: train/validation hold-out and cross validation
The body of train() shows how to partition the data. It holds out a test split first, and then runs cross validation only on the rows that remain:
def main(session):
# Load features
df = session.table('MARKETING_BUDGETS_FEATURES').to_pandas()
X = df.drop('REVENUE', axis = 1)
y = df['REVENUE']
# Split dataset into training and test
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state = 42)
# Preprocess numeric columns
numeric_features = ['SEARCH_ENGINE','SOCIAL_MEDIA','VIDEO','EMAIL']
numeric_transformer = Pipeline(steps=[('poly',PolynomialFeatures(degree = 2)),('scaler', StandardScaler())])
preprocessor = ColumnTransformer(transformers=[('num', numeric_transformer, numeric_features)])
# Create pipeline and train
pipeline = Pipeline(steps=[('preprocessor', preprocessor),('classifier', LinearRegression(n_jobs=-1))])
model = GridSearchCV(pipeline, param_grid={}, n_jobs=-1, cv=10)
model.fit(X_train, y_train)Three details in this code matter. First, train_test_split runs before any preprocessing, and random_state = 42 makes the split repeatable. Second, the StandardScaler and polynomial features sit inside the Pipeline, and the whole pipeline is fitted with model.fit(X_train, y_train). The 20% test rows therefore never influence the scaler or the 10-fold cross validation. Third, the procedure saves the model to @ml_models with session.file.put and then returns an R² score for both train and test. The test score is the hold-out estimate. The same separation appears in the HPO API on the container runtime, where dataset_map pairs a "train" and a "test" DataConnector and passes both to the training function.
Checkpoint 2 of 9· Put it in order
Put the steps of the train() procedure in the order its code runs them
- 1.Return R2 on the train and test data
- 2.Split into X_train/X_test with test_size=0.2
- 3.Dump the model and put it on @ml_models
- 4.Build the preprocessing + LinearRegression Pipeline
- 5.Load MARKETING_BUDGETS_FEATURES into pandas
- 6.Fit GridSearchCV (cv=10) on X_train, y_train
Load, then split, then build and fit the pipeline on the training rows only, then persist the model, then report the train and test scores. Because the split comes before fitting, the test rows stay clean for the final score.
“to load and transform the dataset, which is then loaded into the stored procedure memory to perform pre-processing and ML training”Source: docs.snowflake.com
Checkpoint 3 of 9· Exam question
A data scientist maintains a churn feature pipeline that joins raw event tables and aggregates them hourly. They want Snowflake to keep the feature table fresh without hand-written scheduling code. Select TWO statements that correctly describe using dynamic tables for this pipeline.(Select 2)
Correct answers: B, C — Declare the transformation as a query and set `TARGET_LAG`, and Snowflake schedules refreshes to keep results within that freshness target.; Build a second dynamic table that selects from the first, and Snowflake refreshes upstream tables before downstream ones in the pipeline.
- A. Dynamic tables refresh automatically and can refresh incrementally when the query allows it. Streams plus tasks are an alternative, not a requirement.
- B. Dynamic tables are declarative: you supply the query and a `TARGET_LAG`, and Snowflake decides when to refresh. This removes custom scheduling code.
- C. Dynamic tables can be chained into a dependency graph, and Snowflake orders refreshes so downstream tables see consistent upstream data.
- D. `TARGET_LAG` is a freshness goal with a minimum of about one minute, not a synchronous guarantee. Dynamic tables are not updated in the same transaction as the sources.
- E. A dynamic table is defined by a SELECT query, not by procedural code. Training must be started separately, for example from a task.
3.Hyperparameter tuning and optimization metric selection
In train(), GridSearchCV is given an empty param_grid={}. It cross-validates one configuration and searches nothing. Real tuning needs a search space. For that, Snowflake ML's HPO API on the container runtime runs trials in parallel and can scale across nodes of a compute pool. A *grid search* evaluates every combination of values you define. Sampling functions draw values instead: uniform, loguniform (suited to learning rates, which span orders of magnitude), randint and choice.
search_space = {
"n_estimators": tune.uniform(50, 200),
"max_depth": tune.uniform(3, 10),
"learning_rate": tune.uniform(0.01, 0.3),
}A TunerConfig then states what counts as "best": the metric being optimized, the mode (maximize or minimize), the search algorithm, the number of trials, and the concurrency. Choosing the metric means matching it to the task and to the direction of improvement. Log loss is a loss, so you minimize it. AUC, precision, recall and F1 are scores, so you maximize them. F1 is the documented balance between precision and recall when classes are uneven. For RMSE, the regression metric named in the exam guide, the sources here have no Snowflake-specific guidance. Treat it by the same rule: an error you minimize.
Checkpoint 4 of 9· Check yourself
A tuner should pick the classifier with the best log loss. How should TunerConfig be set?
Log loss is a loss function the model tries to minimize, and the mode setting is what tells the tuner which direction is better.
“Logistic Loss (LogLoss) is calculated for the model as a whole. The objective of prediction is to minimize the loss function.”Source: docs.snowflake.com
4.Down-sampling and up-sampling
Class imbalance is usually handled by changing the training data. Snowflake's built-in tool for down-sampling is the SAMPLE (or TABLESAMPLE) clause. It can sample a fraction of a table or a fixed number of rows:
| Method | Unit sampled | Fraction (probability) | Fixed number of rows |
|---|---|---|---|
| BERNOULLI / ROW (default) | Each row, with probability p/100 | Yes | Yes, returns exactly that many unless the table has fewer |
| SYSTEM / BLOCK | Each block of rows, with probability p/100 | Yes | No |
With a fraction, the number of rows you get back depends on table size and probability. A seed makes the sample deterministic, which matters if you want to reproduce a training run. Apply the hold-out lesson from train() here as well. Resampling changes the training data, so it belongs after the split and only on the training portion. The test split should keep the real class balance. Up-sampling (repeating or synthesizing minority rows) is not covered by the sources for this lesson. That gap is real, and this lesson does not fill it from memory.
Checkpoint 5 of 9· Exam question
A scalar Python UDF applies a log transform and z-score to 80 million rows and runs slowly because Snowflake invokes it once for every row. Which change most directly reduces this per-row overhead?
Correct answer: A — Rewrite it as a vectorized UDF with the `@vectorized` decorator so the handler gets row batches as a pandas DataFrame.
- A. Vectorized Python UDFs receive batches of rows as pandas objects, so vectorized pandas operations replace a Python call per row.
- B. Stored procedures do not change how a UDF is invoked inside a query. Ownership mode controls privileges, not per-row execution cost.
- C. External functions send rows in batches over HTTP to a remote service, which adds network latency. They are not a way to avoid per-row calls.
- D. A UDTF's `process` method is still called per input row, and an empty one would return nothing. It does not remove per-row call overhead.
Checkpoint 6 of 9· Check yourself
You need exactly 40,000 rows sampled from the majority class. Which sampling method can do that?
Only BERNOULLI/ROW accepts a fixed row count. SYSTEM/BLOCK takes only a probability.
“{ SYSTEM | BLOCK } ( <probability> )”Source: docs.snowflake.com
Sources4
5.Training with Python UDTFs and per-partition models
Sometimes the right design is one model per segment, such as per region or per customer group. A Python UDTF fits that shape. The query's OVER (PARTITION BY ...) splits the input, process sees each row of a partition, and end_partition runs after the last row and can yield the partition's result. Snowpark-optimized warehouses can help some UDTF workloads as well as UDFs. The provided UDTF documentation demonstrates the partition mechanics, not a training handler. Treat a UDTF-trains-a-model design as these mechanics applied to fitting, not as a documented recipe.
The documented tool for per-partition training is Many Model Training (MMT). It requires a Snowflake ML container runtime. It partitions a Snowpark DataFrame by a column and trains a separate model on each partition in parallel. Your function receives (data_connector, context), where context.partition_id identifies the partition. MMT serializes the models to a stage for you. XGBoost and scikit-learn are supported out of the box, TorchSerde and TensorFlowSerde cover the deep learning frameworks, and a custom ModelSerde handles anything else.
from snowflake.ml.modeling.distributors.many_model import ManyModelTraining
trainer = ManyModelTraining(train_xgboost_model, "model_stage") # Specify the stage to store the models
training_run = trainer.run(
partition_by="region", # Train separate models for each region
snowpark_dataframe=sales_data,
run_id="regional_models_v1" # Specify a unique ID for the training run
)| Aspect | Python UDTF | Many Model Training |
|---|---|---|
| How partitions are set | OVER (PARTITION BY symbol) in the calling query | partition_by="region" in trainer.run |
| Per-partition hook | __init__ / process / end_partition | Training function (data_connector, context) |
| Output | Tuples yielded to the query result | Models on the stage at run_id/{partition_id} |
| Compute | Warehouse (Snowpark-optimized can help) | Snowflake ML container runtime |
Checkpoint 7 of 9· Check yourself
After the run above, where is the model for the North region stored?
MMT saves each model automatically under run_id/{partition_id} on the stage you name. It survives the session.
“Models are stored in the stage at run_id/{partition_id} where partition_id is the partition column value.”Source: docs.snowflake.com
6.Training outside Snowflake through external functions
If the compute lives outside Snowflake, for example a GPU service, an external function connects it. An external function is a UDF that holds no code of its own. It is a database object that records how to reach a remote service, and it is called with ordinary dot notation in SQL. The remote service must behave like a scalar function. It accepts JSON input, returns JSON output, exposes an HTTPS endpoint, and returns exactly one row for each row received. Examples are an AWS Lambda function, an Azure Function, or an HTTPS server on EC2.
Snowflake never calls the remote service directly. A proxy service such as Amazon API Gateway or Azure API Management relays the requests, and can add authentication. The security details live in an API integration object. These constraints shape any outside-training design: the data arrives row by row as JSON and each row returns one result. Dynamic tables cannot include external functions, so calls to the remote service belong in queries or procedures.
Checkpoint 8 of 9· Exam question
A retailer has one table holding sales history for 1,200 stores and wants a separate demand model per store, trained in parallel inside Snowflake, with each fitted model serialized for later use. Which approach fits best?
Correct answer: C — Write a Python UDTF that buffers rows in `process` and fits in `end_partition`, called with `OVER (PARTITION BY store_id)`.
- A. This works but is sequential, with no parallelism across partitions. A partitioned UDTF spreads the per-store fits across the warehouse.
- B. External functions return one value per input row and are scalar. They cannot return a fitted model per group, and they move data out.
- C. A UDTF called with a partition clause receives each store's rows together, and `end_partition` runs once per store. Different partitions run in parallel.
- D. A scalar UDF sees one row at a time and cannot collect a store's rows. Fitting a model on one row is not meaningful.
Checkpoint 9 of 9· Check yourself
Which statement about calling an external training service is true?
Snowflake calls a proxy service, such as API Gateway or Azure API Management, which relays the data. The external function object holds only how to reach the service, not code.
“Snowflake does not call a remote service directly. Instead, Snowflake calls a proxy service, which relays the data to the remote service.”Source: docs.snowflake.com
Sources6
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.A bigger Snowpark-optimized warehouse spreads one stored-procedure training job across more nodes, so you should size it as large as possible.Why is that wrong?
Stored-procedure training is single-node. The guideline is MEDIUM, which gives exactly one Snowpark-optimized node, with multi-cluster only for concurrency.
Covered in Training with Python stored procedures
2.SAMPLE SYSTEM (n ROWS) is a faster way to get an exact-sized down-sample.Why is that wrong?
SYSTEM/BLOCK samples whole blocks by probability and accepts no fixed row count. Only BERNOULLI/ROW returns an exact number of rows.
Covered in Down-sampling and up-sampling
3.An external function can send a batch to an outside trainer and receive any number of rows back, such as a table of fitted parameters.Why is that wrong?
External functions are scalar. The remote service must return exactly one row for each row it receives, as JSON.
Covered in Training outside Snowflake through external functions
Practise it for real
Train and persist a regression model inside Snowflake with a Python stored procedure on a Snowpark-optimized warehouse
1.With ORGADMIN, go to Admin » Terms in Snowsight and enable Anaconda packages, if no one has done so for this account yet.
Why: The procedure uses scikit-learn from Anaconda and calls to_pandas, and neither works until the External Offerings Terms are accepted.
You should see: The Anaconda section shows the terms as acknowledged.
2.Run CREATE OR REPLACE WAREHOUSE snowpark_opt_wh WITH WAREHOUSE_SIZE = 'MEDIUM' WAREHOUSE_TYPE = 'SNOWPARK-OPTIMIZED' MAX_CONCURRENCY_LEVEL = 1; and use it.
Why: MEDIUM gives exactly one Snowpark-optimized node, the documented setup for single-node training.
You should see: The warehouse is created and set as current.
3.Create the train() procedure from the documented example, with PACKAGES snowflake-snowpark-python, scikit-learn and joblib and HANDLER 'main'. You need a MARKETING_BUDGETS_FEATURES table and an @ml_models stage.
Why: The handler splits 80/20, cross-validates on the training rows, and saves the model to the stage.
You should see: The procedure TRAIN is created successfully.
4.Run CALL train();
Why: Runs the training and the hold-out evaluation in one call.
You should see: A VARIANT with keys 'R2 score on Train' and 'R2 score on Test', and model.joblib uploaded to @ml_models.
Stuck? Get a nudge
If the test R2 is well below the train R2, the model is overfitting. Only the 20% hold-out shows you that.
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.https://docs.snowflake.com/en/developer-guide/snowpark/python/python-snowpark-training-mlOfficial docs
“Snowpark-optimized warehouses make it possible to use Snowpark stored procedures to run single-node ML training workloads directly in Snowflake.”
↩︎ Training with Python stored procedures“Use the session.use_warehouse() API to select the warehouse for the query inside the stored procedure.”
↩︎ Training with Python stored procedures“model = GridSearchCV(pipeline, param_grid={}, n_jobs=-1, cv=10)”
↩︎ Partitioning: train/validation hold-out and cross validation“These optimized warehouses can also benefit some UDF and UDTF scenarios.”
↩︎ Training with Python UDTFs and per-partition models“Set WAREHOUSE_SIZE = MEDIUM to ensure that the Snowpark-optimized warehouse consists of 1 Snowpark-optimized node.”
↩︎ Exam trap 1“to load and transform the dataset, which is then loaded into the stored procedure memory to perform pre-processing and ML training”
↩︎ Checkpoint - 2.
“The dataset_map object is a dictionary that pairs the training or test dataset with its corresponding Snowflake DataConnector object.”
↩︎ Partitioning: train/validation hold-out and cross validation“Within the object, you specify the metric being optimized, the optimization mode, and the other execution parameters.”
↩︎ Hyperparameter tuning and optimization metric selection“Mode Determines whether the objective is to maximize or minimize the metric”
↩︎ Hyperparameter tuning and optimization metric selection“Grid search Explores a grid for hyperparameter values that you define.”
↩︎ Hyperparameter tuning and optimization metric selection - 3.
“It provides a balance between precision and recall, especially when there is an uneven class distribution.”
↩︎ Hyperparameter tuning and optimization metric selection“Logistic Loss (LogLoss) is calculated for the model as a whole. The objective of prediction is to minimize the loss function.”
↩︎ Checkpoint - 4.
“When you sample a fixed, specified number of rows, the query returns the exact number of specified rows unless the table contains fewer rows.”
↩︎ Down-sampling and up-sampling“You can specify a seed to make the sampling deterministic.”
↩︎ Down-sampling and up-sampling“BERNOULLI (or ROW): Includes each row with a probability of p/100.”
↩︎ Exam trap 2“{ SYSTEM | BLOCK } ( <probability> )”
↩︎ Checkpoint - 5.https://docs.snowflake.com/en/developer-guide/snowflake-ml/train-models-across-partitionsOfficial docs
“MMT partitions your Snowpark DataFrame by a specified column and trains separate models on each partition in parallel.”
↩︎ Training with Python UDTFs and per-partition models“MMT requires a Snowflake ML container runtime environment.”
↩︎ Training with Python UDTFs and per-partition models“Models are stored in the stage at run_id/{partition_id} where partition_id is the partition column value.”
↩︎ Checkpoint - 6.
“Accept JSON inputs and return JSON outputs.”
↩︎ Training outside Snowflake through external functions“Snowflake stores security-related external function information in an API integration.”
↩︎ Training outside Snowflake through external functions“Snowflake supports scalar external functions; the remote service must return exactly one row for each row received.”
↩︎ Exam trap 3“Snowflake supports scalar external functions; the remote service must return exactly one row for each row received.”
↩︎ Prediction“Snowflake does not call a remote service directly. Instead, Snowflake calls a proxy service, which relays the data to the remote service.”
↩︎ Checkpoint