OCI Data Science Model Catalog: Store, Version, and Manage Machine Learning Models
You spend three weeks training a machine learning model. It achieves 94% accuracy. Everyone is excited. Then someone asks: "Which exact version of the model is running in production right now? What data was it trained on? Which notebook created it? Can you reproduce it?"
If you've been saving models as random .pkl files in folders called model_final_v3_really_final,
you already know this pain!
The OCI Data Science Model Catalog solves this completely. It's a managed, centralised library for your trained ML models — with versioning, metadata, reproducibility, access control, and a direct path to deployment. Think of it as a professional filing system for every model your team ever trains!
What Is the OCI Model Catalog?
The Simple Explanation
Imagine a public library 📚. Every book has a clear title, author, description, and location number. You can find any book in seconds. You can borrow it and return it. New editions are tracked separately from older ones. The library never loses a book.
The OCI Model Catalog is exactly that — a library for machine learning models. Every trained model gets its own entry with a title (name), description, metadata (who trained it, when, how), and the actual model file stored securely in Oracle-managed storage. You can find any model instantly, share it with your team, load it back into a notebook, or deploy it to production.
WITHOUT a Model Catalog (the chaotic way):
📁 /home/datascience/models/
model_v1.pkl
model_v2.pkl
model_final.pkl
model_final_ACTUALLY_final.pkl
model_PROD_DO_NOT_DELETE.pkl
model_copy_of_prod.pkl ← which one is in production?! 😱
WITH the OCI Model Catalog (the organised way):
OCI Model Catalog
├── customer_churn_predictor (Model Version Set)
│ ├── v1.0 — trained 2026-01-15 — accuracy: 81% — Status: Retired
│ ├── v2.0 — trained 2026-03-01 — accuracy: 87% — Status: Challenger
│ └── v3.0 — trained 2026-06-10 — accuracy: 91% — Status: Champion ← PRODUCTION
└── sales_forecaster (Model Version Set)
├── v1.0 — trained 2026-02-20 — MAPE: 12% — Status: Retired
└── v2.0 — trained 2026-05-18 — MAPE: 8.4% — Status: Champion ← PRODUCTION
Every model is tracked. Every version is documented. Production is always clearly labelled. No more guessing games!
What Does the Model Catalog Store? 📦
Each model entry in the catalog has two main parts:
-
📦 Model Artifact
The actual files needed to load and run the model. This is packaged as a folder (or ZIP) containing:model.pkl(or.h5,.onnx, etc.) — the trained model binaryscore.py— a Python script that tells OCI how to load the model and make predictionsruntime.yaml— specifies which conda environment the model needs to run- Any other supporting files (label encoders, scalers, tokenizers, etc.)
-
📋 Model Metadata
All the documentation about the model — who made it, how, and when. This includes:- Display name and description
- Framework (Scikit-learn, TensorFlow, PyTorch, XGBoost, etc.)
- Algorithm used (Random Forest, Neural Network, etc.)
- Hyperparameters used during training
- Input and output schemas (what features go in, what comes out)
- Provenance — which notebook session or job created the model, which Git commit contains the training code
- Custom metadata (any key-value pairs you want to add!)
Provenance sounds fancy but means something simple: "where did this model come from?" It records which notebook session trained the model, the Git branch and commit used, and the exact training script path. This is critical for reproducibility — if a model starts performing badly in 6 months, you can go back to the provenance, re-run the exact same training code, and understand what changed. Without provenance, debugging production model failures is a nightmare!
The Two Key Files — score.py and runtime.yaml 📄
score.py — The Model's Instruction Manual
When your model is deployed as a live API endpoint,
a small server receives requests and needs to know how to use your model.
The score.py file is the instruction manual that tells the server:
- "Here's how to load the model into memory when the server starts"
- "Here's how to make a prediction when someone sends data to the endpoint"
Think of score.py like the instructions for operating a vending machine.
Someone puts money in (sends input data). The machine knows which product to give back (the prediction).
score.py is what tells the vending machine how to operate!
runtime.yaml — The Environment Specification
Your model was trained using specific Python library versions (Scikit-learn 1.4, NumPy 1.26, etc.). If you try to run it in an environment with different library versions, it might break!
The runtime.yaml file says: "To run this model, you need THIS specific conda environment."
When the model is deployed, OCI automatically installs that exact conda environment on the serving infrastructure.
The model always runs in exactly the right environment — no "but it works on my machine!" problems!
MODEL ARTIFACT STRUCTURE: model_artifact/ ← The ZIP folder you upload to the catalog ├── model.pkl ← The trained model binary (the brains!) ├── label_encoder.pkl ← Supporting file (e.g., for encoding categories) ├── feature_scaler.pkl ← Supporting file (e.g., for normalising inputs) ├── score.py ← Instructions: how to load model and predict ├── runtime.yaml ← Environment specification: which conda env to use ├── input_schema.json ← (Optional) What inputs the model expects └── output_schema.json ← (Optional) What the model outputs look like OCI uses score.py + runtime.yaml to serve your model as a live REST API!
Step-by-Step: Save Your First Model to the Catalog 🛠️
Prerequisites
Before saving a model to the catalog, an OCI administrator must set up these policies:
These IAM policy statements grant your user group permission to save models to the catalog and allow the Data Science service to use Object Storage (where model artifacts are actually stored). Without them, your
.save() call will fail with an "Authorization failed" error!
# Allow your user group to manage models allow group <your-group> to manage data-science-family in tenancy # Allow Data Science service to store model artifacts in Object Storage allow service datascience to manage object-family in tenancy # For large models (>2GB), also allow cross-service access allow service objectstorage-us-ashburn-1 to manage object-family in tenancy
Part 1 — Save a Scikit-learn Model Using ADS (The Easy Way)
This code takes a trained Random Forest model and saves it to the OCI Model Catalog in 4 steps:
- Wraps the model with the ADS SDK (ADS understands Scikit-learn, TensorFlow, PyTorch, and more)
- Calls
prepare()— this is the magic step! ADS automatically writes thescore.pyandruntime.yamlfiles for you. It also creates input/output schemas from sample data - Runs
introspect()to check for common errors before uploading - Calls
save()to upload everything to the Model Catalog and get back a unique model OCID
import ads
import pandas as pd
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from ads.model import SklearnModel
# ── Step 0: Authenticate with OCI ───────────────────────────────────────────
# Inside a notebook session, Resource Principal is the recommended method
ads.set_auth(auth="resource_principal")
# ── Step 0b: Train a sample model (you would use your real trained model here)
np.random.seed(42)
n = 5000
X = pd.DataFrame({
"age": np.random.randint(18, 70, n),
"balance": np.random.uniform(0, 100000, n),
"num_products": np.random.randint(1, 5, n),
"tenure_years": np.random.randint(0, 15, n),
"credit_score": np.random.randint(350, 850, n),
})
y = (X["balance"] < 5000).astype(int) # 1 = high churn risk, 0 = low churn risk
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
model = RandomForestClassifier(n_estimators=100, max_depth=6, random_state=42)
model.fit(X_train, y_train)
accuracy = (model.predict(X_test) == y_test).mean()
print(f"✅ Model trained! Test accuracy: {accuracy*100:.1f}%\n")
# ── Step 1: Wrap with ADS SklearnModel ───────────────────────────────────────
# ADS understands Scikit-learn and knows how to package it for OCI deployment
ads_model = SklearnModel(
estimator=model,
kind="model",
artifact_dir="./churn_model_artifact" # Local folder where files are prepared
)
# ── Step 2: Prepare the artifact (ADS auto-generates score.py + runtime.yaml!)
print("📦 Preparing model artifact...")
ads_model.prepare(
# Which conda environment will serve this model in production?
# Use an OCI-managed conda env slug — this one has Scikit-learn pre-installed
inference_conda_env="generalml_p311_cpu_x86_64_v1",
# Sample input data — ADS uses this to auto-generate the input schema
X_sample=X_test.head(5),
# Sample output — ADS uses this to auto-generate the output schema
y_sample=model.predict(X_test.head(5)),
force_overwrite=True # Overwrite the artifact_dir if it already exists
)
print("✅ Artifact prepared! Files created:")
import os
for f in os.listdir("./churn_model_artifact"):
print(f" 📄 {f}")
# ── Step 3: Introspect — run checks before uploading ─────────────────────────
# This is like spell-checking your document before sending it to the boss!
# It validates that score.py and runtime.yaml are correct and complete.
print("\n🔍 Running artifact validation checks...")
ads_model.introspect()
# ── Step 4: Save to the OCI Model Catalog ───────────────────────────────────
print("\n☁️ Uploading model to OCI Model Catalog...")
COMPARTMENT_ID = "ocid1.compartment.oc1..aaaaaaaa...your-compartment-id..."
PROJECT_ID = "ocid1.datascienceproject.oc1..aaaaaaaa...your-project-id..."
catalog_model = ads_model.save(
display_name="customer_churn_predictor_v1",
description=(
"Random Forest classifier predicting customer churn risk. "
"Input features: age, balance, num_products, tenure_years, credit_score. "
f"Test accuracy: {accuracy*100:.1f}%. Trained on synthetic banking dataset."
),
project_id=PROJECT_ID,
compartment_id=COMPARTMENT_ID,
# Add taxonomy metadata — helps your team discover and understand the model
taxonomy_metadata={
"UseCaseType": "binary_classification",
"Framework": "scikit-learn",
"FrameworkVersion": "1.4",
"Algorithm": "RandomForestClassifier",
"Hyperparameters": str({"n_estimators": 100, "max_depth": 6}),
},
ignore_pending_changes=True
)
print(f"\n🎉 Model saved to OCI Model Catalog!")
print(f" Model OCID: {catalog_model.id}")
print(f" Display Name: {catalog_model.display_name}")
print(f" Status: {catalog_model.lifecycle_state}")
print(f"\n💡 You can now:")
print(f" 1. Load this model into any notebook using its OCID")
print(f" 2. Deploy it as a live REST API endpoint from OCI Console")
print(f" 3. Share it with your team — they can access it via the OCID")
Example output:
✅ Model trained! Test accuracy: 88.3% 📦 Preparing model artifact... ✅ Artifact prepared! Files created: 📄 model.pkl 📄 score.py 📄 runtime.yaml 📄 input_schema.json 📄 output_schema.json 🔍 Running artifact validation checks... ✅ score.py has load_model() function ✅ score.py has predict() function ✅ runtime.yaml has valid conda environment reference ✅ Model file found: model.pkl ☁️ Uploading model to OCI Model Catalog... 🎉 Model saved to OCI Model Catalog! Model OCID: ocid1.datasciencemodel.oc1.iad.aaaaaaaa... Display Name: customer_churn_predictor_v1 Status: ACTIVE 💡 You can now: 1. Load this model into any notebook using its OCID 2. Deploy it as a live REST API endpoint from OCI Console 3. Share it with your team — they can access it via the OCID
What score.py Looks Like — Demystified 🔍
Let's look at what ADS actually generated in score.py
so you understand what's happening under the hood:
When OCI deploys your model as a live API endpoint, this is the script that runs on the server. It has exactly two important functions:
load_model()— runs ONCE when the server starts up. It loads your model file from disk into memory.predict()— runs EVERY TIME someone calls your API. It takes the incoming JSON data, feeds it to the model, and returns the prediction.
# This is score.py — the file ADS auto-generated for you
# You can view it at: ./churn_model_artifact/score.py
import os
import json
import pickle
import numpy as np
import pandas as pd
from functools import lru_cache
# ── FUNCTION 1: load_model() ──────────────────────────────────────────────────
# Called ONCE when the model deployment server starts up.
# It loads your model file from disk into memory so it's ready to serve predictions.
@lru_cache(maxsize=10)
def load_model(model_file_name="model.pkl"):
"""Load the trained model from the artifact directory."""
model_dir = os.path.dirname(os.path.realpath(__file__))
model_path = os.path.join(model_dir, model_file_name)
with open(model_path, "rb") as file:
model = pickle.load(file)
print(f"✅ Model loaded from: {model_path}")
return model
# ── FUNCTION 2: predict() ─────────────────────────────────────────────────────
# Called EVERY TIME someone sends a prediction request to your API endpoint.
# It receives JSON data, processes it, runs inference, and returns the result.
def predict(data, model=load_model()):
"""
Make predictions on incoming data.
Args:
data: JSON payload (dict) sent to the API endpoint
Expected format: {"data": {"columns": [...], "data": [...]}}
model: Loaded model object from load_model()
Returns:
dict: Prediction results in JSON format
"""
# Convert incoming JSON to a Pandas DataFrame
# The API sends data in the format: {"data": {"columns": [...], "data": [...]}}
input_df = pd.DataFrame.from_dict(data["data"])
# Run the model prediction
predictions = model.predict(input_df)
probabilities = model.predict_proba(input_df)
# Return predictions in a structured JSON format
return {
"prediction": predictions.tolist(), # 0 or 1 (churn / no churn)
"probability_churn": probabilities[:, 1].tolist(), # Probability of churning
"probability_stay": probabilities[:, 0].tolist() # Probability of staying
}
That's all there is to it! Two functions. ADS generates this automatically based on your model type — you rarely need to edit it. But when you do (for custom preprocessing, special output formats, etc.) you now know exactly what to change!
Loading a Model Back from the Catalog 📥
Once a model is in the catalog, anyone on your team can load it back into their own notebook session — without needing to retrain it or find the original training code. They just need the model's OCID.
This code retrieves a previously saved model from the OCI Model Catalog. Give it the model's OCID (the unique ID that was printed when you saved it) and it downloads the model artifact back to your notebook session. Then you can immediately use the loaded model to make predictions — no retraining needed! Perfect for: reviewing a model someone else trained, making batch predictions on new data, or comparing versions.
import ads
import pandas as pd
from ads.model import SklearnModel
import tempfile
# ── Set authentication ────────────────────────────────────────────────────────
ads.set_auth(auth="resource_principal")
# ── The OCID of the model you want to load ────────────────────────────────────
# This was printed when the model was saved.
# Share this OCID with your teammates so they can load the same model!
MODEL_OCID = "ocid1.datasciencemodel.oc1.iad.aaaaaaaa...your-model-ocid..."
print(f"📥 Loading model from OCI Model Catalog...")
print(f" Model OCID: {MODEL_OCID[:40]}...")
# ── Load the model from the catalog ──────────────────────────────────────────
# from_model_catalog() downloads the artifact and deserialises the model
loaded_model = SklearnModel.from_model_catalog(
model_id=MODEL_OCID,
artifact_dir=tempfile.mkdtemp(), # Temp folder to store downloaded artifact
ignore_conda_error=True # Don't fail if local conda env differs
)
print(f"\n✅ Model loaded successfully!")
print(f" Display name: {loaded_model.metadata_provenance.training_script}")
# ── Access the actual model object for predictions ────────────────────────────
model_object = loaded_model.estimator # The original RandomForestClassifier
# ── Make predictions using the loaded model ───────────────────────────────────
new_customers = pd.DataFrame({
"age": [25, 55, 38],
"balance": [2500, 87000, 15000],
"num_products": [1, 3, 2],
"tenure_years": [1, 12, 5],
"credit_score": [450, 720, 650],
})
predictions = model_object.predict(new_customers)
probabilities = model_object.predict_proba(new_customers)
print("\n📊 PREDICTIONS ON NEW CUSTOMERS:")
print("-" * 55)
for i, (pred, prob) in enumerate(zip(predictions, probabilities[:, 1])):
risk = "🚨 HIGH CHURN RISK" if pred == 1 else "✅ LOW CHURN RISK"
print(f" Customer {i+1}: {risk} (churn probability: {prob:.1%})")
Example output:
📥 Loading model from OCI Model Catalog... Model OCID: ocid1.datasciencemodel.oc1.iad.aaaaaaaa... ✅ Model loaded successfully! 📊 PREDICTIONS ON NEW CUSTOMERS: ------------------------------------------------------- Customer 1: 🚨 HIGH CHURN RISK (churn probability: 76.3%) Customer 2: ✅ LOW CHURN RISK (churn probability: 11.8%) Customer 3: ✅ LOW CHURN RISK (churn probability: 34.2%)
Feature: Model Version Sets — Track Model Evolution 📈
What Are Model Version Sets?
A Model Version Set is a logical group that contains multiple versions of the same model. Think of it like a family album 📷 — it contains photos of the same person at different ages. Each photo (model version) captures a moment in time, and together they tell the story of how the model evolved.
Model Version Sets are essential for:
- 🏆 Champion/Challenger — Track which model is "champion" (in production) and which is the "challenger" (being evaluated)
- 🔬 A/B Testing — Run two model versions simultaneously and compare real-world performance
- 📜 Audit Trail — Regulators and auditors can see the full history of every model version
- 🔄 Easy Rollback — If v3 performs badly in production, you can quickly switch back to v2 with confidence
This demonstrates creating a Model Version Set and adding two model versions to it — one trained with 100 trees ("challenger") and one with 200 trees ("champion"). Each version gets a label so the team knows which model is which. In production, you'd retrain periodically, add each retrained model to the Version Set, and update labels to reflect which model is currently winning. Think of it like a sports tournament bracket — the champion label follows whoever is performing best!
import ads
import numpy as np
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from ads.model import SklearnModel
from ads.model.model_version_set import ModelVersionSet
ads.set_auth(auth="resource_principal")
COMPARTMENT_ID = "ocid1.compartment.oc1..aaaaaaaa...your-compartment-id..."
PROJECT_ID = "ocid1.datascienceproject.oc1..aaaaaaaa...your-project-id..."
# ── Generate sample training data ─────────────────────────────────────────────
np.random.seed(42)
n = 5000
X = pd.DataFrame({
"age": np.random.randint(18, 70, n),
"balance": np.random.uniform(0, 100000, n),
"num_products": np.random.randint(1, 5, n),
"tenure_years": np.random.randint(0, 15, n),
"credit_score": np.random.randint(350, 850, n),
})
y = (X["balance"] < 5000).astype(int)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# ── STEP 1: Create a Model Version Set ───────────────────────────────────────
# This is the "album" that will hold all versions of the churn predictor model
print("📁 Creating Model Version Set...")
mvs = ModelVersionSet.create(
name="customer-churn-predictor",
description=(
"Version set tracking all iterations of the customer churn prediction model. "
"Models trained on banking customer data for binary churn classification."
),
compartment_id=COMPARTMENT_ID,
project_id=PROJECT_ID
)
print(f"✅ Version Set created! ID: {mvs.id}\n")
# ── STEP 2: Train and save Model Version 1 (100 trees — the baseline) ─────────
print("🌲 Training Model Version 1 (100 trees)...")
model_v1 = RandomForestClassifier(n_estimators=100, max_depth=6, random_state=42)
model_v1.fit(X_train, y_train)
acc_v1 = accuracy_score(y_test, model_v1.predict(X_test))
print(f" V1 Accuracy: {acc_v1*100:.1f}%")
# Wrap, prepare, and save V1 to the Version Set
ads_v1 = SklearnModel(estimator=model_v1, artifact_dir="./artifact_v1")
ads_v1.prepare(
inference_conda_env="generalml_p311_cpu_x86_64_v1",
X_sample=X_test.head(5),
force_overwrite=True
)
saved_v1 = ads_v1.save(
display_name="churn_predictor_v1",
description=f"Baseline RF model. 100 trees. Accuracy: {acc_v1*100:.1f}%",
project_id=PROJECT_ID,
compartment_id=COMPARTMENT_ID,
# Link this model to the Version Set
model_version_set_id=mvs.id,
version_label="Challenger" # Start as challenger until we know if it's the best
)
print(f" ✅ V1 saved to catalog. OCID: {saved_v1.id[:30]}...\n")
# ── STEP 3: Train and save Model Version 2 (200 trees — improved) ─────────────
print("🌲🌲 Training Model Version 2 (200 trees)...")
model_v2 = RandomForestClassifier(n_estimators=200, max_depth=8, random_state=42)
model_v2.fit(X_train, y_train)
acc_v2 = accuracy_score(y_test, model_v2.predict(X_test))
print(f" V2 Accuracy: {acc_v2*100:.1f}%")
ads_v2 = SklearnModel(estimator=model_v2, artifact_dir="./artifact_v2")
ads_v2.prepare(
inference_conda_env="generalml_p311_cpu_x86_64_v1",
X_sample=X_test.head(5),
force_overwrite=True
)
saved_v2 = ads_v2.save(
display_name="churn_predictor_v2",
description=f"Improved RF model. 200 trees, deeper. Accuracy: {acc_v2*100:.1f}%",
project_id=PROJECT_ID,
compartment_id=COMPARTMENT_ID,
model_version_set_id=mvs.id,
version_label="Champion" # V2 outperforms V1, so label it Champion!
)
print(f" ✅ V2 saved to catalog. OCID: {saved_v2.id[:30]}...\n")
# ── STEP 4: List all models in the Version Set ────────────────────────────────
print("📋 MODEL VERSION SET CONTENTS:")
print("=" * 55)
for model_entry in mvs.models():
print(f" 🏷️ Label: {model_entry.version_label}")
print(f" 📛 Name: {model_entry.display_name}")
print(f" 📅 Created: {str(model_entry.time_created)[:10]}")
print(f" 🔑 OCID: {model_entry.id[:35]}...")
print()
Example output:
📁 Creating Model Version Set... ✅ Version Set created! ID: ocid1.datasciencemodelv... 🌲 Training Model Version 1 (100 trees)... V1 Accuracy: 87.2% ✅ V1 saved to catalog. OCID: ocid1.datasciencemodel.oc1.iad... 🌲🌲 Training Model Version 2 (200 trees)... V2 Accuracy: 89.1% ✅ V2 saved to catalog. OCID: ocid1.datasciencemodel.oc1.iad... 📋 MODEL VERSION SET CONTENTS: ======================================================= 🏷️ Label: Challenger 📛 Name: churn_predictor_v1 📅 Created: 2026-04-12 🔑 OCID: ocid1.datasciencemodel.oc1.iad.aaaaaaaa... 🏷️ Label: Champion 📛 Name: churn_predictor_v2 📅 Created: 2026-04-12 🔑 OCID: ocid1.datasciencemodel.oc1.iad.aaaaaaaa...
Deploying a Model from the Catalog — One More Step to Live! 🚀
Once your model is in the catalog, deploying it as a live REST API endpoint is straightforward. The catalog entry contains everything the deployment service needs — the model files, the environment spec, the prediction code.
THE PATH FROM TRAINING TO PRODUCTION:
📓 Notebook Session ← You train the model here
│
│ ads_model.save()
▼
🗃️ Model Catalog ← Model stored safely with all metadata
│
│ Deploy (from Console OR from ADS)
▼
⚡ Model Deployment ← Live HTTPS endpoint serving predictions
│
│ POST /predict
▼
📱 Your Application ← Calls the endpoint, gets predictions back!
This code takes a model that's already in the Model Catalog and deploys it as a live REST API endpoint. OCI automatically provisions a load balancer, configures the model serving container with the right conda environment, and gives you a public HTTPS URL. After the deployment is ACTIVE (takes 5-15 minutes), any application anywhere in the world can call that URL to get predictions! It's like turning your trained model into a live web service that other apps can talk to.
import ads
from ads.model.deployment import ModelDeployment, ModelDeploymentContainerRuntime, ModelDeploymentInfrastructure
ads.set_auth(auth="resource_principal")
COMPARTMENT_ID = "ocid1.compartment.oc1..aaaaaaaa...your-compartment-id..."
PROJECT_ID = "ocid1.datascienceproject.oc1..aaaaaaaa...your-project-id..."
# The OCID of the model you want to deploy (the Champion model from our Version Set)
MODEL_OCID = "ocid1.datasciencemodel.oc1.iad.aaaaaaaa...champion-model-ocid..."
print(f"🚀 Deploying model as a live REST API endpoint...")
print(f" This takes 5-15 minutes — perfect time for a coffee! ☕\n")
# ── Build the deployment configuration ───────────────────────────────────────
deployment = (
ModelDeployment()
.with_display_name("churn-predictor-prod")
.with_description("Production churn prediction endpoint. Champion model v2.")
# ── Compute infrastructure settings ──────────────────────────────────────
.with_infrastructure(
ModelDeploymentInfrastructure()
.with_project_id(PROJECT_ID)
.with_compartment_id(COMPARTMENT_ID)
.with_shape_name("VM.Standard.E4.Flex") # CPU VM for serving
.with_shape_config_details(
ocpus=2, # 2 CPUs for the model server
memory_in_gbs=16 # 16 GB RAM
)
.with_replica(1) # Start with 1 server instance
.with_bandwidth_mbps(10) # Load balancer bandwidth
)
# ── Runtime settings — how to run the model ───────────────────────────────
.with_runtime(
ModelDeploymentContainerRuntime()
.with_model_uri(MODEL_OCID) # Point to the champion model in catalog
.with_server_port(8080)
.with_health_check_port(8080)
.with_env({"LOG_LEVEL": "INFO"}) # Pass environment variables if needed
)
)
# ── Create the deployment ─────────────────────────────────────────────────────
deployment.deploy(
wait_for_completion=True, # Wait until ACTIVE before returning
max_wait_time=600 # Wait up to 10 minutes
)
print(f"\n✅ Deployment is ACTIVE!")
print(f" Deployment OCID: {deployment.model_deployment_id}")
# ── Test the endpoint with a real prediction request ─────────────────────────
import requests
import json
endpoint_url = deployment.url + "/predict"
# Send a prediction request — customer with high churn risk
request_payload = {
"data": {
"columns": ["age", "balance", "num_products", "tenure_years", "credit_score"],
"data": [[28, 1200, 1, 1, 420]] # Young customer, low balance, new
}
}
print(f"\n📡 Sending test prediction to endpoint...")
print(f" URL: {endpoint_url}")
response = deployment.predict(request_payload)
print(f"\n🎯 Prediction result:")
print(json.dumps(response, indent=2))
Example output:
🚀 Deploying model as a live REST API endpoint...
This takes 5-15 minutes — perfect time for a coffee! ☕
✅ Deployment is ACTIVE!
Deployment OCID: ocid1.datasciencemodeldeployment.oc1.iad.aaaaaaaa...
📡 Sending test prediction to endpoint...
URL: https://modeldeployment.us-ashburn-1.oci.customer-oci.com/ocid1.../predict
🎯 Prediction result:
{
"prediction": [1],
"probability_churn": [0.823],
"probability_stay": [0.177]
}
The model is live! 🎉 Any application — a mobile app, a web dashboard, a CRM system — can now call this HTTPS endpoint and get an instant churn prediction. No Python knowledge required on the calling side — just a standard HTTP POST request!
Browsing the Catalog from the OCI Console 🖥️
You don't always need code to work with the Model Catalog. The OCI Console provides a full visual interface for browsing, searching, and managing your models.
- Step 1: OCI Console → hamburger menu ☰ → Analytics & AI → Data Science
- Step 2: Click on your Project → click Models in the left menu
- Step 3: You see a list of all models with: name, framework, creation date, and status
- Step 4: Click any model to see: full metadata, provenance, taxonomy, artifact details, and test results
- Step 5: From the model detail page, click Deploy to deploy directly from the console (no code needed!)
Model Artifact Size Limits — What You Need to Know 📏
Depending on how you save the model, different size limits apply:
- 📤 Via OCI Console: Maximum 100 MB artifact size — fine for traditional ML models (Scikit-learn, XGBoost, LightGBM)
- 📤 Via ADS SDK / OCI CLI: No size limit for standard models
- 🤖 Large Model support (2024+): Artifacts up to 400 GB — for hosting large language models, deep learning models with massive weights. First upload to Object Storage, then transfer to the catalog
For large models (TensorFlow, PyTorch, or LLMs — anything over 2GB), the standard save path changes slightly. You first upload the artifact to an OCI Object Storage bucket, and then ADS transfers it from there to the Model Catalog. This "staging via Object Storage" pattern handles models of any size, including multi-gigabyte neural network weights.
import ads
from ads.model import TensorFlowModel
ads.set_auth(auth="resource_principal")
COMPARTMENT_ID = "ocid1.compartment.oc1..aaaaaaaa...your-compartment-id..."
PROJECT_ID = "ocid1.datascienceproject.oc1..aaaaaaaa...your-project-id..."
NAMESPACE = "your-object-storage-namespace"
BUCKET_NAME = "large-model-staging"
# Assume `tf_model` is a trained TensorFlow/Keras model
# ads_tf_model = TensorFlowModel(estimator=tf_model, artifact_dir="./tf_artifact")
# ads_tf_model.prepare(...)
# For large models, specify bucket_uri — the model is staged in Object Storage first
large_model_catalog_entry = ads_tf_model.save(
display_name="image_classifier_resnet50_v1",
description="ResNet-50 fine-tuned on product defect images. 400MB artifact.",
project_id=PROJECT_ID,
compartment_id=COMPARTMENT_ID,
# ↓ This triggers the large-model upload path through Object Storage
bucket_uri=f"oci://{BUCKET_NAME}@{NAMESPACE}/model-staging/",
overwrite_existing_artifact=True
)
print(f"✅ Large model saved! OCID: {large_model_catalog_entry.id}")
Custom Metadata — Add Any Information You Need 🏷️
Beyond the standard taxonomy fields, you can add completely custom key-value pairs to any model. This is useful for tracking business-specific information that OCI doesn't have a standard field for.
This adds custom metadata to a model after it's already been saved to the catalog. Think of it like adding sticky notes to a book after it's been filed in the library. Custom metadata can include business context (which product team owns this model, which business unit it serves), performance metrics (AUC, F1 score, MAPE), compliance information (approval status, reviewer name), or anything else your team tracks!
import ads
from ads.model import ModelMetadataItem
ads.set_auth(auth="resource_principal")
MODEL_OCID = "ocid1.datasciencemodel.oc1.iad.aaaaaaaa...your-model-ocid..."
# ── Load the existing model from the catalog ──────────────────────────────────
from ads.model import SklearnModel
import tempfile
model_entry = SklearnModel.from_model_catalog(
MODEL_OCID,
artifact_dir=tempfile.mkdtemp(),
ignore_conda_error=True
)
# ── Update the model with custom metadata ────────────────────────────────────
model_entry.update(
# Custom metadata — any key-value pairs you need!
custom_metadata_list=[
ModelMetadataItem(
key="BusinessUnit",
value="Retail Banking — Customer Success",
description="Which business unit owns this model",
category="Performance"
),
ModelMetadataItem(
key="AUC_Score",
value="0.921",
description="Area Under the ROC Curve on hold-out test set",
category="Performance"
),
ModelMetadataItem(
key="F1_Score",
value="0.887",
description="F1 Score on hold-out test set",
category="Performance"
),
ModelMetadataItem(
key="ApprovalStatus",
value="Approved",
description="Model risk committee approval status",
category="other"
),
ModelMetadataItem(
key="ReviewedBy",
value="Dr. Sarah Chen — Head of Model Risk",
description="Name of model reviewer who approved production use",
category="other"
),
ModelMetadataItem(
key="NextReviewDate",
value="2026-10-01",
description="Date by which this model must be re-evaluated",
category="other"
),
]
)
print("✅ Custom metadata added to model!")
print(" Model now has complete business context, performance metrics, and compliance info.")
print(" All visible in OCI Console → Data Science → Models → Your Model")
Common Mistakes and How to Avoid Them 🚨
If
score.py has a syntax error or is missing the predict() function,
the model will save to the catalog but fail when you try to deploy it.
ads_model.introspect() catches these issues before upload. Always run it!
If you train with TensorFlow 2.14 but specify a conda env with TensorFlow 2.10, your deployed model will crash. Use the same conda environment slug for training AND inference. When in doubt, check the exact package versions with
pip freeze and match them in your published conda environment.
If your model needs a label encoder (
label_encoder.pkl) or a feature scaler (scaler.pkl) to make predictions,
those files MUST be inside the artifact folder alongside model.pkl.
If they're in a different folder, they won't be uploaded to the catalog and the deployed model will crash when it can't find them.
Everything the model needs at inference time must be in the artifact folder!
Saving a model with only a name and no description, no taxonomy, no provenance information is a missed opportunity. In 6 months, when the model behaves unexpectedly, you'll have no way to understand what data it was trained on, who trained it, or what hyperparameters were used. Metadata is cheap to add at save time but priceless for debugging later!
Model artifacts in the OCI catalog are immutable by design. You cannot edit or replace the artifact of a saved model entry. If you need to update the model, save it as a new version in the Model Version Set. This immutability is actually a feature — it guarantees that whatever model is in production today can always be traced back to the exact artifact in the catalog, byte for byte.
Quick Summary 📝
What we learned about OCI Data Science Model Catalog:
- Model Catalog → A managed, centralised library for trained ML models. Versioned, searchable, auditable, and directly connected to deployment.
- Model Artifact → The ZIP folder containing: model binary,
score.py,runtime.yaml, schemas, and supporting files. - score.py → The instruction manual. Has
load_model()(runs once at startup) andpredict()(runs on every API call). - runtime.yaml → Specifies which conda environment the serving infrastructure must install to run the model correctly.
- ADS SDK → Auto-generates score.py and runtime.yaml, runs introspection checks, and handles the entire save/load workflow with minimal code.
- introspect() → Pre-upload validation. Catches score.py and runtime.yaml errors before you waste time uploading a broken artifact.
- Model Version Sets → Groups multiple versions of the same model. Supports Champion/Challenger labelling, A/B testing, and full audit trails.
- from_model_catalog() → Load any saved model back into any notebook session using just the model OCID. Perfect for team sharing.
- Custom Metadata → Add any business-specific information: performance metrics, approval status, reviewer name, review dates.
- Immutable Artifacts → Saved model artifacts cannot be changed. This is a feature — it guarantees reproducibility and auditability!
- Large Model Support → Models up to 400 GB supported via Object Storage staging path.
Happy cataloguing! 🗃️☁️
Comments
Post a Comment