-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
73e4901
commit e693dd1
Showing
8 changed files
with
650 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
0.5 1.5 -0.2 |
Oops, something went wrong.