What you will be able to do
- Write a custom MLflow pyfunc model that splits one-time loading from per-request preprocessing
- Package a pyfunc model so Model Serving can build it
- Compute features at inference time with Unity Catalog Python UDFs, FeatureFunction and fe.log_model
- Handle missing lookup values and UDF dependencies in batch scoring and in online serving
1.Custom model objects with mlflow.pyfunc
Sometimes the transformation has to happen inside the model when a request arrives, not in an upstream pipeline. MLflow's Python function flavor, pyfunc, packages any Python code as a model. Databricks lists the cases where you need it: the model needs preprocessing before inputs reach predict, the framework is not natively supported by MLflow, raw outputs need post-processing, the model branches per request, or you want to deploy fully custom code.
A custom model subclasses mlflow.pyfunc.PythonModel and has two required functions with different jobs.
In this example, load_context loads the weights and the tokenizer from context.artifacts. predict runs the per-request feature work (format_inputs), scores the model, and post-processes the result (format_outputs).
class CustomModel(mlflow.pyfunc.PythonModel):
def load_context(self, context):
self.model = torch.load(context.artifacts["model-weights"])
from preprocessing_utils.my_custom_tokenizer import CustomTokenizer
self.tokenizer = CustomTokenizer(context.artifacts["tokenizer_cache"])
def format_inputs(self, model_input):
# insert some code that formats your inputs
pass
def format_outputs(self, outputs):
predictions = (torch.sigmoid(outputs)).data.numpy()
return predictions
def predict(self, context, model_input):
model_input = self.format_inputs(model_input)
outputs = self.model.predict(model_input)
return self.format_outputs(outputs)Checkpoint 1 of 5· Check yourself
In the CustomModel above, where does the per-request work such as format_inputs and format_outputs get called from?
predict houses the logic that runs on each request, so it calls format_inputs, scores the model and calls format_outputs. load_context is only for one-time loading.
“predict - this function houses all the logic that is run every time an input request is made.”Source: docs.databricks.com
Sources1
2.Packaging a pyfunc so it can be served
Feature-engineering code is often shared across models. With code_path, the model carries that code, so the import in load_context above works after loading. For example: mlflow.pyfunc.log_model(CustomModel(), "model", code_path = ["preprocessing_utils/"]).
The runtime also catches people out. Databricks Runtime ML includes mlflow-skinny by default, not the full mlflow package. If you log a pyfunc there without pip_requirements, the model's conda.yaml captures mlflow-skinny, and Model Serving cannot build the container. Pin mlflow explicitly:
# DBR ML ships with mlflow-skinny by default, so specify mlflow explicitly
# to ensure Model Serving compatibility.
mlflow.pyfunc.log_model(
name="model",
python_model=your_model,
pip_requirements=["mlflow==3.8.1"], # use mlflow, not mlflow-skinny
registered_model_name="catalog.schema.model_name",
)Before deploying, check that the model can actually be served by using mlflow.models.predict. You can then register the model to Unity Catalog and serve it from a Model Serving endpoint.
Checkpoint 2 of 5· Match them up
Match each pyfunc element to what it does
Tap a term, then the definition that fits it.
load_context is one-time setup and predict is per-request work. code_path brings your own modules along, and pinning mlflow prevents the mlflow-skinny build failure.
“predict - this function houses all the logic that is run every time an input request is made.”Source: docs.databricks.com
Sources1
3.On-demand features: computing features at inference time
A hand-written pyfunc is one way to compute features per request. Feature Engineering in Unity Catalog offers a managed alternative: on-demand features. These are features whose values are not known ahead of time and are calculated at inference from request-time inputs. You define the calculation as a Python UDF governed by Unity Catalog. This requires a Unity Catalog-enabled workspace and Databricks Runtime 13.3 LTS ML or above.
During training, you reference the UDF with a FeatureFunction and map its arguments to columns with input_bindings. You pass it to create_training_set in feature_lookups, alongside ordinary FeatureLookups. Here, x comes from the request and y comes from a feature table:
FeatureFunction(
udf_name="main.default.example_feature", # UDF must be in Unity Catalog so uses a three-level namespace
input_bindings={
"x": "new_source_input",
"y": "materialized_feature_value"
},
output_name="on_demand_feature",
),The request-side input, new_source_input, must be in the training DataFrame. At inference it must be in the DataFrame you pass to score_batch, or in the real-time request. The step that makes the feature logic travel with the model is logging: you must log with the Feature Store log_model method. After that, score_batch calculates every feature, including on-demand ones, and Model Serving runs the UDF for each request. For Feature Views, the equivalent building block is a CustomUDF with a RequestSource for request-time inputs.
Checkpoint 3 of 5· Put it in order
Put the on-demand feature workflow in order
- 1.Score with score_batch or Model Serving, which compute the on-demand feature automatically
- 2.Pass a FeatureFunction for that UDF to create_training_set in feature_lookups
- 3.Create a Python UDF in Unity Catalog that computes the feature
- 4.Train the model and log it with the Feature Store log_model method
The UDF has to exist before it can be bound in training. Logging through the Feature Store client is what lets batch scoring and serving re-run it automatically.
“During training, you provide this function and its input bindings in the feature_lookups parameter of the create_training_set API.”Source: docs.databricks.com
Checkpoint 4 of 5· Fill the gap
Which registry URI must be set so this feature-aware model registers to Unity Catalog?
import mlflow
mlflow.set_registry_uri(" ? ")
fe.log_model(
model=model,
artifact_path="main.default.model",
flavor=mlflow.sklearn,
training_set=training_set,
registered_model_name="main.default.recommender_model"
)Setting the registry URI to databricks-uc before calling fe.log_model registers the feature-aware model in Unity Catalog. Its feature tables must also be stored in Unity Catalog.
Source: docs.databricks.comSources2
4.Missing values and dependencies: batch vs online
The same UDF runs in two environments, and they do not behave the same way. A UDF that takes a looked-up feature as input receives a different missing value depending on where it runs, and dependencies reach the two environments by different routes.
| Concern | score_batch (offline) | Model Serving (online) |
|---|---|---|
| Lookup key not found, value passed to the UDF | None | float("nan") |
| Packages the UDF imports | Declare them with extra_pip_requirements in fe.log_model | Declare them with extra_pip_requirements in fe.log_model |
| UDF ENVIRONMENT clause | Configures dependencies for offline SQL execution | Not copied into the online serving environment |
A robust UDF handles both missing-value forms. You can also give a FeatureLookup a fallback with default_values. If you renamed the feature with rename_outputs, the default has to use the renamed name.
%sql
CREATE OR REPLACE FUNCTION square(x INT)
RETURNS INT
LANGUAGE PYTHON AS
$$
import numpy as np
if x is None or np.isnan(x):
return 0
return x * x
$$Checkpoint 5 of 5· Check yourself
An on-demand UDF works in score_batch but fails in Model Serving when a lookup key is missing. It checks only x is None. Why?
Batch scoring passes None for a missing key, but online serving passes float("nan"). The UDF must check for both.
“When using score_batch, the value returned is None. When using online serving, the value returned is float("nan").”Source: docs.databricks.com
Sources2
Exam traps
Each one states something that sounds right. Open it to see what is actually true.
1.A model trained on a training set with a FeatureFunction can be logged with plain mlflow.sklearn.log_model and will still compute on-demand features at inference.Why is that wrong?
Only a model logged with the Feature Store log_model method carries the feature metadata that makes batch scoring and serving evaluate on-demand features automatically.
Covered in On-demand features: computing features at inference time
2.A pyfunc logged on Databricks Runtime ML without pip_requirements will serve fine, because MLflow is already installed there.Why is that wrong?
DBR ML ships mlflow-skinny, so conda.yaml captures it, and Model Serving cannot build the image. Pin mlflow==<version> in pip_requirements.
Covered in Packaging a pyfunc so it can be served
3.Dependencies declared in the UDF's ENVIRONMENT clause are also available when the model is served online.Why is that wrong?
The ENVIRONMENT clause covers offline SQL execution only. For a served model, list the packages in extra_pip_requirements when calling fe.log_model.
Sources
Every claim above is drawn from one of these pages, quoted as it was written on the date shown.
- 1.https://docs.databricks.com/aws/en/machine-learning/model-serving/deploy-custom-python-codeOfficial docs
“Your model requires preprocessing before inputs can be passed to the model's predict function.”
↩︎ Custom model objects with mlflow.pyfunc“There are two required functions when packaging arbitrary python code with MLflow:”
↩︎ Custom model objects with mlflow.pyfunc“With the code_path parameter, authors of models can log full code references that load into the path”
↩︎ Packaging a pyfunc so it can be served“Always specify mlflow==<version> in pip_requirements when you call mlflow.pyfunc.log_model() on a Databricks Runtime ML runtime”
↩︎ Packaging a pyfunc so it can be served“Model Serving requires mlflow (not mlflow-skinny) in conda.yaml and cannot build the container image otherwise.”
↩︎ Exam trap 2“anything that needs to be loaded just one time for the model to operate should be defined in this function”
↩︎ Prediction“predict - this function houses all the logic that is run every time an input request is made.”
↩︎ Checkpoint“predict - this function houses all the logic that is run every time an input request is made.”
↩︎ Checkpoint - 2.
“on-demand features are computed at inference time using Python user-defined functions (UDFs).”
↩︎ On-demand features: computing features at inference time“When you serve a model with Model Serving, the model automatically uses the Python UDF to compute on-demand features for each scoring request.”
↩︎ On-demand features: computing features at inference time“you must specify these packages using the argument extra_pip_requirements.”
↩︎ Missing values and dependencies: batch vs online“If the feature columns are renamed using the rename_outputs parameter, default_values must use the renamed feature names.”
↩︎ Missing values and dependencies: batch vs online“You must log the trained model using the Feature Store method log_model.”
↩︎ Exam trap 1“It does not copy those dependencies into the online serving environment.”
↩︎ Exam trap 3“During training, you provide this function and its input bindings in the feature_lookups parameter of the create_training_set API.”
↩︎ Checkpoint“When using score_batch, the value returned is None. When using online serving, the value returned is float("nan").”
↩︎ Checkpoint