Skip to content

Commit

Permalink
doc: add exmaple
Browse files Browse the repository at this point in the history
  • Loading branch information
kyuwoo-choi committed Nov 21, 2024
1 parent 73e4901 commit e693dd1
Show file tree
Hide file tree
Showing 8 changed files with 650 additions and 1 deletion.
71 changes: 71 additions & 0 deletions example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# # nubison-model example

This is an example of how to use the `nubison-model` library to register a user model.

## ## Prerequisites

- mlflow server is running on `http://127.0.0.1:5000` or set `MLFLOW_TRACKING_URI` environment variable.

## ## Example structure

```
example/
├── model.ipynb
├── requirements.txt
└── src/
└── SimpleLinearModel.py
```

- `model.ipynb`: A notebook file that shows how to register a user model and test it.
- `requirements.txt`: A file that specifies the dependencies of the model.
- `src/`: A directory that contains the source code of the model.

## ## How to register a user model and test it

The `model.ipynb` file shows how to register a user model. It contains the following steps:

1. Define a user model.
2. Register the user model.
3. Test the model.

### ### Define a user model

- The user model should be a class that implements the `NubisonModel` protocol.
- The `load_model` method is used to load the model weights from the file.
- The `infer` method is used to return the inference result.

#### #### `load_model` method

- Use this method to prepare the model for inference which can be time-consuming.
- This method is called once when the model inference server starts.
- The path to the model weights file can be specified relative.

#### #### `infer` method

- This method is called for each inference request.
- This method can take any number of arguments.
- The return and argument types of the `infer` method can be `int`, `float`, `str`, `list`(`Tensor`, `ndarray`) and `dict`.

### ### Register a user model

- The `register` function is used to register the user model.
- The `artifact_dirs` argument specifies the folders containing the files used by the model class.
- If the model class does not use any files, this argument can be omitted.

### ### Test a user model

- The `test_client` function is used to test the model.
- It can be used to test the model through HTTP requests.

## ## requirements.txt

- The `requirements.txt` file is used to specify the dependencies of the model.
- The packages listed here will be installed in the environment where the model is deployed.
- If no `requirements.txt` file is provided, current environment packages will be used.

## ## src

- The `src/` directory contains the source code of the model.
- The name `src/` can be changed to any other name and additional folders can be added. The folders should be specified in the `artifact_dirs` argument of the `register` function.
- The files in those folders should be imported using absolute paths from `model.ipynb`.
- Both relative and absolute paths can be used when importing inside the `src/` directory. Check the `SimpleLinearModel.py` and `utils/logger.py` for more details.
165 changes: 165 additions & 0 deletions example/model.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# # User Model Definition"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"# The `NubisonModel` class serves as a base class for creating custom user model classes.\n",
"from nubison_model import NubisonModel\n",
"\n",
"class UserModel(NubisonModel):\n",
" \"\"\"\n",
" A user model that extends the `NubisonModel` base class.\n",
" \"\"\"\n",
" def load_model(self) -> None:\n",
" \"\"\"\n",
" This method is used to load the model weights from the file.\n",
" \"\"\"\n",
" try:\n",
" # Import the SimpleLinearModel class from the src directory\n",
" # This class implements a basic linear model for text processing\n",
" # Using absolute import path to ensure reliable imports across different execution contexts\n",
" from src.SimpleLinearModel import SimpleLinearModel\n",
" self.model = SimpleLinearModel(\"./src/weights.txt\")\n",
" except Exception as e:\n",
" print(f\"Error: {e}\")\n",
"\n",
" def infer(self, x1: float, x2: float):\n",
" \"\"\"\n",
" This method is used to return the inference result.\n",
" \"\"\"\n",
" return {\"y\": self.model.calc(x1, x2)}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# # Model Register"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2024/11/21 11:55:16 WARNING mlflow.utils.requirements_utils: Detected one or more mismatches between the model's dependencies and the current Python environment:\n",
" - nubison-model (current: 0.0.2.dev3+3e1558a.20241118053748, required: nubison-model==0.0.1)\n",
"To fix the mismatches, call `mlflow.pyfunc.get_model_dependencies(model_uri)` to fetch the model's environment and install dependencies using the resulting environment file.\n",
"2024/11/21 11:55:16 WARNING mlflow.models.model: Model logged without a signature and input example. Please set `input_example` parameter when logging the model to auto infer the model signature.\n",
"Registered model 'Default' already exists. Creating a new version of this model...\n",
"2024/11/21 11:55:16 INFO mlflow.store.model_registry.abstract_store: Waiting up to 300 seconds for model version to finish creation. Model name: Default, version 139\n",
"Created version '139' of model 'Default'.\n",
"2024/11/21 11:55:16 INFO mlflow.tracking._tracking_service.client: 🏃 View run hilarious-hound-941 at: http://127.0.0.1:5000/#/experiments/0/runs/7a07fb6fa06549558fbbb35778a3a938.\n",
"2024/11/21 11:55:16 INFO mlflow.tracking._tracking_service.client: 🧪 View experiment at: http://127.0.0.1:5000/#/experiments/0.\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model registered: runs:/7a07fb6fa06549558fbbb35778a3a938/\n"
]
}
],
"source": [
"# The `register` function is utilized to register the user-defined model with the system,\n",
"from nubison_model import register\n",
"\n",
"# Register the user model\n",
"# The `artifact_dirs` argument specifies the folders containing the files used by the model class.\n",
"model_id = register(UserModel(), artifact_dirs=\"src\")\n",
"print(f\"Model registered: {model_id}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# # Model Testing"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2024/11/21 11:55:18 WARNING mlflow.utils.requirements_utils: Detected one or more mismatches between the model's dependencies and the current Python environment:\n",
" - nubison-model (current: 0.0.2.dev3+3e1558a.20241118053748, required: nubison-model==0.0.1)\n",
"To fix the mismatches, call `mlflow.pyfunc.get_model_dependencies(model_uri)` to fetch the model's environment and install dependencies using the resulting environment file.\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"2024/11/21 11:55:18 WARNING mlflow.utils.requirements_utils: Detected one or more mismatches between the model's dependencies and the current Python environment:\n",
" - nubison-model (current: 0.0.2.dev3+3e1558a.20241118053748, required: nubison-model==0.0.1)\n",
"To fix the mismatches, call `mlflow.pyfunc.get_model_dependencies(model_uri)` to fetch the model's environment and install dependencies using the resulting environment file.\n",
"2024-11-21 11:55:18,653 - SimpleLinearModel - INFO - Weights loaded successfully from ./src/weights.txt.\n",
"INFO:SimpleLinearModel:Weights loaded successfully from ./src/weights.txt.\n",
"2024-11-21 11:55:18,671 - SimpleLinearModel - INFO - Calculating the result of the linear model with x1=3.1, x2=2.0.\n",
"INFO:SimpleLinearModel:Calculating the result of the linear model with x1=3.1, x2=2.0.\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Prepared artifact: src -> /tmp/tmpttu4qyi4/artifacts/src\n",
"The result of the linear model is 4.35.\n"
]
}
],
"source": [
"# The `test_client` function is used to test the model.\n",
"from nubison_model.Service import test_client\n",
"\n",
"# Create a test client for the model.\n",
"with test_client(model_id) as client:\n",
" # The request should include the parameters defined in the `infer` method.\n",
" result = client.post(\"/infer\", json={\"x1\": 3.1, \"x2\": 2})\n",
" print(f\"The result of the linear model is {result.json()['y']}.\")\n",
"\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"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.9.20"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
7 changes: 7 additions & 0 deletions example/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# This file is used to specify the dependencies of your model.
# The packages listed here will be installed in the environment where the model is deployed.

nubison-model==0.0.1 # nubison-model is required to register the user model.

# Put other required packages here.
#numpy==2.1.3
36 changes: 36 additions & 0 deletions example/src/SimpleLinearModel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Modules under artifact folders can be imported using both relative or absolute paths.
from .utils.logger import get_logger


class SimpleLinearModel:
"""
A simple linear model.
"""

def __init__(self, weight_path):
"""
Initialize the model.
"""
self.logger = get_logger("SimpleLinearModel")
self.load_weight(weight_path)

def load_weight(self, weight_path):
"""
Load the weights from the file.
"""
try:
with open(weight_path, "r") as file:
self.weights = [float(w) for w in file.read().split()]
self.logger.info(f"Weights loaded successfully from {weight_path}.")
except FileNotFoundError:
self.logger.error(f"The file at {weight_path} was not found.")
raise

def calc(self, x1: float, x2: float):
"""
Calculate the result of the linear model.
"""
self.logger.info(
f"Calculating the result of the linear model with x1={x1}, x2={x2}."
)
return self.weights[0] * x1 + self.weights[1] * x2 + self.weights[2]
17 changes: 17 additions & 0 deletions example/src/utils/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import logging


def get_logger(name: str) -> logging.Logger:
"""
Configures and returns a simple example logger.
"""
logger = logging.getLogger(name)
if not logger.handlers:
handler = logging.StreamHandler()
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
return logger
1 change: 1 addition & 0 deletions example/src/weights.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.5 1.5 -0.2
Loading

0 comments on commit e693dd1

Please sign in to comment.