Study guide
4.4 Deploying a Custom Model to a Model Endpoint
Databricks Certified Machine Learning Associate Study Guide Exam
Track your progress as you read
Finished sections get a check mark.
1 Chapter 1: Databricks Machine Learning (38% of the Exam)
1.1 MLOps Best Practices on Databricks 1.2 Advantages of Databricks ML Runtimes and Compute Selection 1.3 Promoting Code vs. Promoting Models 1.4 How AutoML Facilitates Model and Feature Selection 1.5 AutoML Advantages in the Model Development Process 1.6 Feature Tables in Unity Catalog vs. the Workspace Feature Store 1.7 Creating a Feature Table and Writing Data to It 1.8 Training a Model with Features from a Feature Table 1.9 Scoring a Model with Features from a Feature Table 1.10 Online vs. Offline Feature Tables 1.11 Manually Logging Metrics, Artifacts, and Models in an MLflow Run 1.12 Identifying the Best Run with the MLflow Client API 1.13 Information Available in the MLflow UI 1.14 Registering a Model in the Unity Catalog Registry 1.15 Model Tags, Aliases, and Champion/Challenger Promotion
2 Chapter 2: Data Processing and ML Workflows (19% of the Exam)
2.1 Computing Summary Statistics on a Spark DataFrame 2.2 Creating Visualizations for Categorical and Continuous Features 2.3 Comparing Two Categorical or Two Continuous Features 2.4 Removing Outliers with Standard Deviation and IQR 2.5 When Log-Scale Transformation Is Appropriate 2.6 Comparing and Applying Mean, Median, and Mode Imputation 2.7 One-Hot Encoding and When It Is or Is Not Appropriate
3 Chapter 3: Model Development (31% of the Exam)
3.1 Selecting the Appropriate Algorithm for a Scenario 3.2 Methods to Mitigate Data Imbalance in Training Data 3.3 Comparing Estimators and Transformers 3.4 Developing a Training Pipeline 3.5 Tuning Hyperparameters with Hyperopt's fmin 3.6 Grid, Random, and Bayesian Search Strategies 3.7 Parallelizing Single-Node Models for Hyperparameter Tuning 3.8 Cross-Validation vs. a Train-Validation Split 3.9 Counting the Models Trained by Grid Search and Cross-Validation 3.10 Using Common Classification Metrics 3.11 Using Common Regression Metrics 3.12 Choosing the Most Appropriate Metric for the Objective 3.13 Exponentiating Log-Transformed Predictions Before Evaluation 3.14 Model Complexity and the Bias-Variance Tradeoff
4 Chapter 4: Model Deployment (12% of the Exam)
4.1 Differences and Advantages of Batch, Streaming, and Real-Time Serving 4.2 Performing Batch Inference with pandas and Spark UDFs 4.3 Streaming Inference with Delta Live Tables 4.4 Deploying a Custom Model to a Model Endpoint 4.5 Deploying, Querying, and Splitting Traffic Across Real-Time Endpoints
4.4 Deploying a Custom Model to a Model Endpoint
Key Takeaways Explain This Topic AI
A custom model is any model wrapped in mlflow.pyfunc.PythonModel , which exposes a predict(context, model_input) method Databricks can serve like any built-in flavour.
load_context loads artifacts once per replica at startup; predict runs per request, so expensive setup belongs in load_context .
Log a custom model with mlflow.pyfunc.log_model(python_model=..., artifacts=..., pip_requirements=..., signature=...) , then register it in Unity Catalog.
Deploy by creating a serving endpoint whose served entity names the registered model and version or alias, with a workload size and scale_to_zero_enabled .
The logged signature and pinned dependencies are what make the endpoint reproducible; a missing dependency is the most common cause of a failed endpoint build.
Last updated: August 2026
- 4.4 Deploying a Custom Model to a Model Endpoint
- Not every deployable model is a plain scikit-learn estimator. A production model
- frequently needs pre-processing, post-processing, business rules, an ensemble of
several estimators, or a lookup table bundled alongside the weights. MLflow's pyfunc custom model is the mechanism that packages all of that behind one uniform predict interface, and Databricks Model Serving deploys it exactly like any built-in
flavour.
- Step 1 — Wrap the Logic in mlflow.pyfunc.PythonModel import mlflow
- import pandas as pd
class RiskScorer(mlflow.pyfunc.PythonModel):
def load_context(self, context):
- """Runs ONCE per serving replica when the container starts."""
- import joblib, json
- self.model = joblib.load(context.artifacts["classifier"])
with open(context.artifacts["thresholds"]) as f:
self.thresholds = json.load(f)
def predict(self, context, model_input: pd.DataFrame) -> pd.DataFrame:
- """Runs on EVERY request."""
- # Pre-processing that must match training exactly
- features = model_input.assign(
- spend_ratio=model_input["monthly_spend"] / model_input["credit_limit"]
- )[self.model.feature_names_in_]
proba = self.model.predict_proba(features)[:, 1]
- # Post-processing: business banding the raw estimator cannot express
- band = pd.cut(proba,
- bins=[0, self.thresholds["low"], self.thresholds["high"], 1],
- labels=["low", "medium", "high"])
return pd.DataFrame({"probability": proba, "risk_band": band.astype(str)})
The split between the two methods is the part exam questions probe:
Method Runs Put here
load_context(context) Once per replica at container start Deserialising weights, reading config files, opening a tokenizer
- predict(context, model_input) On every request Only per-request work
- Loading the model inside predict would repeat expensive deserialisation on every
call and destroy the endpoint's latency profile.
Step 2 — Log It with Artifacts, Dependencies, and a Signature from mlflow.models import infer_signature
signature = infer_signature(sample_input_df, sample_output_df)
with mlflow.start_run(run_name="risk_scorer_custom"):
- mlflow.pyfunc.log_model(
- artifact_path="model",
- python_model=RiskScorer(),
- artifacts={
- "classifier": "/dbfs/tmp/risk_clf.joblib",
- "thresholds": "/dbfs/tmp/thresholds.json",
- },
- pip_requirements=["scikit-learn==1.4.2", "pandas==2.2.1", "joblib==1.4.0"],
- signature=signature,
- input_example=sample_input_df.head(3),
- registered_model_name="prod_ml.risk.risk_scorer",
- )
- artifacts is a dictionary of files copied into the model package. Their paths
- are resolved through context.artifacts at load time — never hard-code a DBFS path
inside predict , because the serving container may not have that mount.
- pip_requirements pins the environment the endpoint rebuilds. Omitting a
- transitive dependency is the single most common cause of an endpoint that fails to
start.
- signature declares the input and output schema. Unity Catalog registration
- requires it, and the endpoint validates incoming requests against it, so a malformed
payload returns a clear error instead of a silent misprediction.
- Step 3 — Create the Serving Endpoint from databricks.sdk import WorkspaceClient
- from databricks.sdk.service.serving import EndpointCoreConfigInput, ServedEntityInput
w = WorkspaceClient()
- w.serving_endpoints.create_and_wait(
- name="risk-scorer-endpoint",
- config=EndpointCoreConfigInput(
- served_entities=[
- ServedEntityInput(
- name="risk-scorer-v1",
- entity_name="prod_ml.risk.risk_scorer", # 3-level UC name
- entity_version="1",
- workload_size="Small",
- scale_to_zero_enabled=True,
- )
- ]
- ),
- )
- The same endpoint can be created from the Serving UI or the REST API. Whichever route
- is used, three things must be true: the model is registered in Unity Catalog, the
serving identity can read and execute it, and the logged environment resolves.
Sizing and Access Control
Two parts of that configuration decide whether the endpoint survives production:
Setting Values What it controls
workload_size Small (0–4), Medium (8–16), Large (16–64) Provisioned concurrency — how many simultaneous requests the endpoint is sized for
workload_type CPU , CPU_MEDIUM , CPU_LARGE , GPU_SMALL , GPU_MEDIUM , MULTIGPU_MEDIUM , GPU_MEDIUM_8 Whether each replica runs on CPU or a GPU instance
scale_to_zero_enabled True / False Whether idle replicas shut down. Databricks does not recommend it for production endpoints, because capacity is not guaranteed when scaling back up
Permissions are validated at create and update time, and the requirement is three
grants rather than one: USE CATALOG on the catalog, USE SCHEMA on the schema, and EXECUTE on the model. A custom pyfunc that reads a second governed table at request
time needs its own grants on that table too.
- Shipping Helper Code with the Model
- RiskScorer above is self-contained, but a real custom model usually imports project
modules. Those modules do not exist inside the serving container unless you send them:
code_paths=["./src/features"] copies the listed files or directories into the
model package and puts them on sys.path at load time.
mlflow.models.set_model(RiskScorer()) is the models-from-code alternative:
- point python_model at a .py file ending in that call and MLflow records the
- source instead of pickling the object, which sidesteps the class-not-found errors a
pickled custom model raises once its defining notebook is gone.
- Changing an existing endpoint uses the update-config path rather than create . The new
- served entity is built and brought up before traffic moves, so a failed build leaves
the running version serving.
Step 4 — Query It import requests
- response = requests.post(
- f"{host}/serving-endpoints/risk-scorer-endpoint/invocations",
- headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
- json={"dataframe_records": [
- {"monthly_spend": 2400.0, "credit_limit": 8000.0, "tenure_months": 36}
- ]},
- )
- print(response.json()["predictions"])
- Because the custom model returns a DataFrame with probability and risk_band , the
- endpoint's response carries both fields — the post-processing lives inside the deployed
artifact rather than in every calling application.
Failure Modes Specific to Custom Models
Symptom Cause Fix
Endpoint stays in CREATING then fails A dependency in the logged environment cannot be resolved Pin pip_requirements explicitly; verify the model loads in a clean cluster first
FileNotFoundError at container start predict or load_context hard-codes a DBFS path instead of using context.artifacts Pass every file through the artifacts dictionary
Requests rejected with a schema error Payload does not match the logged signature Correct the payload, or relog with a signature inferred from a representative example
First request after idle is slow, later ones are fast Scale-to-zero cold start Expected behaviour; disable scale-to-zero if the latency SLO forbids it
Latency far higher than in the notebook Model deserialisation placed in predict instead of load_context Move the loading into load_context