clarifai 11.1.4rc2__py3-none-any.whl → 11.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clarifai/__init__.py +1 -1
- clarifai/cli/model.py +21 -10
- clarifai/client/model.py +4 -4
- clarifai/client/workflow.py +2 -2
- clarifai/runners/dockerfile_template/Dockerfile.template +1 -32
- clarifai/runners/models/model_builder.py +14 -13
- clarifai/runners/models/model_run_locally.py +5 -2
- clarifai/runners/server.py +21 -8
- clarifai/runners/utils/const.py +1 -1
- clarifai/utils/misc.py +12 -0
- {clarifai-11.1.4rc2.dist-info → clarifai-11.1.5.dist-info}/METADATA +26 -15
- clarifai-11.1.5.dist-info/RECORD +101 -0
- {clarifai-11.1.4rc2.dist-info → clarifai-11.1.5.dist-info}/WHEEL +1 -1
- clarifai/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/__pycache__/errors.cpython-310.pyc +0 -0
- clarifai/__pycache__/versions.cpython-310.pyc +0 -0
- clarifai/cli/__main__.py~ +0 -4
- clarifai/cli/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/cli/__pycache__/__main__.cpython-310.pyc +0 -0
- clarifai/cli/__pycache__/base.cpython-310.pyc +0 -0
- clarifai/cli/__pycache__/compute_cluster.cpython-310.pyc +0 -0
- clarifai/cli/__pycache__/deployment.cpython-310.pyc +0 -0
- clarifai/cli/__pycache__/model.cpython-310.pyc +0 -0
- clarifai/cli/__pycache__/nodepool.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/app.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/base.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/dataset.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/input.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/lister.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/model.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/module.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/runner.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/search.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/user.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/workflow.cpython-310.pyc +0 -0
- clarifai/client/auth/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/client/auth/__pycache__/helper.cpython-310.pyc +0 -0
- clarifai/client/auth/__pycache__/register.cpython-310.pyc +0 -0
- clarifai/client/auth/__pycache__/stub.cpython-310.pyc +0 -0
- clarifai/constants/__pycache__/dataset.cpython-310.pyc +0 -0
- clarifai/constants/__pycache__/model.cpython-310.pyc +0 -0
- clarifai/constants/__pycache__/search.cpython-310.pyc +0 -0
- clarifai/datasets/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/datasets/export/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/datasets/export/__pycache__/inputs_annotations.cpython-310.pyc +0 -0
- clarifai/datasets/upload/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/datasets/upload/__pycache__/base.cpython-310.pyc +0 -0
- clarifai/datasets/upload/__pycache__/features.cpython-310.pyc +0 -0
- clarifai/datasets/upload/__pycache__/image.cpython-310.pyc +0 -0
- clarifai/datasets/upload/__pycache__/text.cpython-310.pyc +0 -0
- clarifai/datasets/upload/__pycache__/utils.cpython-310.pyc +0 -0
- clarifai/models/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/models/model_serving/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/models/model_serving/__pycache__/constants.cpython-310.pyc +0 -0
- clarifai/models/model_serving/cli/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/models/model_serving/cli/__pycache__/_utils.cpython-310.pyc +0 -0
- clarifai/models/model_serving/cli/__pycache__/base.cpython-310.pyc +0 -0
- clarifai/models/model_serving/cli/__pycache__/build.cpython-310.pyc +0 -0
- clarifai/models/model_serving/cli/__pycache__/create.cpython-310.pyc +0 -0
- clarifai/models/model_serving/model_config/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/models/model_serving/model_config/__pycache__/base.cpython-310.pyc +0 -0
- clarifai/models/model_serving/model_config/__pycache__/config.cpython-310.pyc +0 -0
- clarifai/models/model_serving/model_config/__pycache__/inference_parameter.cpython-310.pyc +0 -0
- clarifai/models/model_serving/model_config/__pycache__/output.cpython-310.pyc +0 -0
- clarifai/models/model_serving/model_config/triton/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/models/model_serving/model_config/triton/__pycache__/serializer.cpython-310.pyc +0 -0
- clarifai/models/model_serving/model_config/triton/__pycache__/triton_config.cpython-310.pyc +0 -0
- clarifai/models/model_serving/model_config/triton/__pycache__/wrappers.cpython-310.pyc +0 -0
- clarifai/models/model_serving/repo_build/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/models/model_serving/repo_build/__pycache__/build.cpython-310.pyc +0 -0
- clarifai/models/model_serving/repo_build/static_files/__pycache__/base_test.cpython-310-pytest-7.2.0.pyc +0 -0
- clarifai/rag/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/rag/__pycache__/rag.cpython-310.pyc +0 -0
- clarifai/rag/__pycache__/utils.cpython-310.pyc +0 -0
- clarifai/runners/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/runners/__pycache__/server.cpython-310.pyc +0 -0
- clarifai/runners/dockerfile_template/Dockerfile.debug +0 -11
- clarifai/runners/dockerfile_template/Dockerfile.debug~ +0 -9
- clarifai/runners/models/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/runners/models/__pycache__/base_typed_model.cpython-310.pyc +0 -0
- clarifai/runners/models/__pycache__/model_builder.cpython-310.pyc +0 -0
- clarifai/runners/models/__pycache__/model_class.cpython-310.pyc +0 -0
- clarifai/runners/models/__pycache__/model_run_locally.cpython-310.pyc +0 -0
- clarifai/runners/models/__pycache__/model_runner.cpython-310.pyc +0 -0
- clarifai/runners/models/__pycache__/model_servicer.cpython-310.pyc +0 -0
- clarifai/runners/models/__pycache__/model_upload.cpython-310.pyc +0 -0
- clarifai/runners/models/model_upload.py +0 -607
- clarifai/runners/utils/#const.py# +0 -30
- clarifai/runners/utils/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/const.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/data_handler.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/data_utils.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/loader.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/logging.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/url_fetcher.cpython-310.pyc +0 -0
- clarifai/schema/__pycache__/search.cpython-310.pyc +0 -0
- clarifai/urls/__pycache__/helper.cpython-310.pyc +0 -0
- clarifai/utils/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/utils/__pycache__/logging.cpython-310.pyc +0 -0
- clarifai/utils/__pycache__/misc.cpython-310.pyc +0 -0
- clarifai/utils/__pycache__/model_train.cpython-310.pyc +0 -0
- clarifai/workflows/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/workflows/__pycache__/export.cpython-310.pyc +0 -0
- clarifai/workflows/__pycache__/utils.cpython-310.pyc +0 -0
- clarifai/workflows/__pycache__/validate.cpython-310.pyc +0 -0
- clarifai-11.1.4rc2.dist-info/RECORD +0 -194
- {clarifai-11.1.4rc2.dist-info → clarifai-11.1.5.dist-info}/LICENSE +0 -0
- {clarifai-11.1.4rc2.dist-info → clarifai-11.1.5.dist-info}/entry_points.txt +0 -0
- {clarifai-11.1.4rc2.dist-info → clarifai-11.1.5.dist-info}/top_level.txt +0 -0
@@ -1,607 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import re
|
3
|
-
import sys
|
4
|
-
import tarfile
|
5
|
-
import time
|
6
|
-
from string import Template
|
7
|
-
|
8
|
-
import yaml
|
9
|
-
from clarifai_grpc.grpc.api import resources_pb2, service_pb2
|
10
|
-
from clarifai_grpc.grpc.api.status import status_code_pb2
|
11
|
-
from google.protobuf import json_format
|
12
|
-
from rich import print
|
13
|
-
from rich.markup import escape
|
14
|
-
|
15
|
-
from clarifai.client import BaseClient
|
16
|
-
from clarifai.runners.utils.const import (
|
17
|
-
AVAILABLE_PYTHON_IMAGES, AVAILABLE_TORCH_IMAGES, CONCEPTS_REQUIRED_MODEL_TYPE,
|
18
|
-
DEFAULT_PYTHON_VERSION, PYTHON_BUILDER_IMAGE, PYTHON_RUNTIME_IMAGE, TORCH_BASE_IMAGE)
|
19
|
-
from clarifai.runners.utils.loader import HuggingFaceLoader
|
20
|
-
from clarifai.urls.helper import ClarifaiUrlHelper
|
21
|
-
from clarifai.utils.logging import logger
|
22
|
-
|
23
|
-
|
24
|
-
def _clear_line(n: int = 1) -> None:
|
25
|
-
LINE_UP = '\033[1A' # Move cursor up one line
|
26
|
-
LINE_CLEAR = '\x1b[2K' # Clear the entire line
|
27
|
-
for _ in range(n):
|
28
|
-
print(LINE_UP, end=LINE_CLEAR, flush=True)
|
29
|
-
|
30
|
-
|
31
|
-
class ModelUploader:
|
32
|
-
|
33
|
-
def __init__(self, folder: str, validate_api_ids: bool = True, download_validation_only=False):
|
34
|
-
"""
|
35
|
-
:param folder: The folder containing the model.py, config.yaml, requirements.txt and
|
36
|
-
checkpoints.
|
37
|
-
:param validate_api_ids: Whether to validate the user_id and app_id in the config file. TODO(zeiler):
|
38
|
-
deprecate in favor of download_validation_only.
|
39
|
-
:param download_validation_only: Whether to skip the API config validation. Set to True if
|
40
|
-
just downloading a checkpoint.
|
41
|
-
"""
|
42
|
-
self._client = None
|
43
|
-
if not validate_api_ids: # for backwards compatibility
|
44
|
-
download_validation_only = True
|
45
|
-
self.download_validation_only = download_validation_only
|
46
|
-
self.folder = self._validate_folder(folder)
|
47
|
-
self.config = self._load_config(os.path.join(self.folder, 'config.yaml'))
|
48
|
-
self._validate_config()
|
49
|
-
self.model_proto = self._get_model_proto()
|
50
|
-
self.model_id = self.model_proto.id
|
51
|
-
self.model_version_id = None
|
52
|
-
self.inference_compute_info = self._get_inference_compute_info()
|
53
|
-
self.is_v3 = True # Do model build for v3
|
54
|
-
|
55
|
-
def _validate_folder(self, folder):
|
56
|
-
if folder == ".":
|
57
|
-
folder = "" # will getcwd() next which ends with /
|
58
|
-
if not os.path.isabs(folder):
|
59
|
-
folder = os.path.join(os.getcwd(), folder)
|
60
|
-
logger.info(f"Validating folder: {folder}")
|
61
|
-
if not os.path.exists(folder):
|
62
|
-
raise FileNotFoundError(f"Folder {folder} not found, please provide a valid folder path")
|
63
|
-
files = os.listdir(folder)
|
64
|
-
assert "config.yaml" in files, "config.yaml not found in the folder"
|
65
|
-
# If just downloading we don't need requirements.txt or the python code, we do need the
|
66
|
-
# 1/ folder to put 1/checkpoints into.
|
67
|
-
assert "1" in files, "Subfolder '1' not found in the folder"
|
68
|
-
if not self.download_validation_only:
|
69
|
-
assert "requirements.txt" in files, "requirements.txt not found in the folder"
|
70
|
-
subfolder_files = os.listdir(os.path.join(folder, '1'))
|
71
|
-
assert 'model.py' in subfolder_files, "model.py not found in the folder"
|
72
|
-
return folder
|
73
|
-
|
74
|
-
@staticmethod
|
75
|
-
def _load_config(config_file: str):
|
76
|
-
with open(config_file, 'r') as file:
|
77
|
-
config = yaml.safe_load(file)
|
78
|
-
return config
|
79
|
-
|
80
|
-
def _validate_config_checkpoints(self):
|
81
|
-
"""
|
82
|
-
Validates the checkpoints section in the config file.
|
83
|
-
:return: loader_type the type of loader or None if no checkpoints.
|
84
|
-
:return: repo_id location of checkpoint.
|
85
|
-
:return: hf_token token to access checkpoint.
|
86
|
-
"""
|
87
|
-
assert "type" in self.config.get("checkpoints"), "No loader type specified in the config file"
|
88
|
-
loader_type = self.config.get("checkpoints").get("type")
|
89
|
-
if not loader_type:
|
90
|
-
logger.info("No loader type specified in the config file for checkpoints")
|
91
|
-
return None, None, None
|
92
|
-
assert loader_type == "huggingface", "Only huggingface loader supported for now"
|
93
|
-
if loader_type == "huggingface":
|
94
|
-
assert "repo_id" in self.config.get("checkpoints"), "No repo_id specified in the config file"
|
95
|
-
repo_id = self.config.get("checkpoints").get("repo_id")
|
96
|
-
|
97
|
-
# get from config.yaml otherwise fall back to HF_TOKEN env var.
|
98
|
-
hf_token = self.config.get("checkpoints").get("hf_token", os.environ.get("HF_TOKEN", None))
|
99
|
-
return loader_type, repo_id, hf_token
|
100
|
-
|
101
|
-
def _check_app_exists(self):
|
102
|
-
resp = self.client.STUB.GetApp(service_pb2.GetAppRequest(user_app_id=self.client.user_app_id))
|
103
|
-
if resp.status.code == status_code_pb2.SUCCESS:
|
104
|
-
return True
|
105
|
-
return False
|
106
|
-
|
107
|
-
def _validate_config_model(self):
|
108
|
-
assert "model" in self.config, "model section not found in the config file"
|
109
|
-
model = self.config.get('model')
|
110
|
-
assert "user_id" in model, "user_id not found in the config file"
|
111
|
-
assert "app_id" in model, "app_id not found in the config file"
|
112
|
-
assert "model_type_id" in model, "model_type_id not found in the config file"
|
113
|
-
assert "id" in model, "model_id not found in the config file"
|
114
|
-
if '.' in model.get('id'):
|
115
|
-
logger.error(
|
116
|
-
"Model ID cannot contain '.', please remove it from the model_id in the config file")
|
117
|
-
sys.exit(1)
|
118
|
-
|
119
|
-
assert model.get('user_id') != "", "user_id cannot be empty in the config file"
|
120
|
-
assert model.get('app_id') != "", "app_id cannot be empty in the config file"
|
121
|
-
assert model.get('model_type_id') != "", "model_type_id cannot be empty in the config file"
|
122
|
-
assert model.get('id') != "", "model_id cannot be empty in the config file"
|
123
|
-
|
124
|
-
if not self._check_app_exists():
|
125
|
-
logger.error(
|
126
|
-
f"App {self.client.user_app_id.app_id} not found for user {self.client.user_app_id.user_id}"
|
127
|
-
)
|
128
|
-
sys.exit(1)
|
129
|
-
|
130
|
-
def _validate_config(self):
|
131
|
-
if not self.download_validation_only:
|
132
|
-
self._validate_config_model()
|
133
|
-
|
134
|
-
assert "inference_compute_info" in self.config, "inference_compute_info not found in the config file"
|
135
|
-
|
136
|
-
if self.config.get("concepts"):
|
137
|
-
model_type_id = self.config.get('model').get('model_type_id')
|
138
|
-
assert model_type_id in CONCEPTS_REQUIRED_MODEL_TYPE, f"Model type {model_type_id} not supported for concepts"
|
139
|
-
|
140
|
-
if self.config.get("checkpoints"):
|
141
|
-
loader_type, _, hf_token = self._validate_config_checkpoints()
|
142
|
-
|
143
|
-
if loader_type == "huggingface" and hf_token:
|
144
|
-
is_valid_token = HuggingFaceLoader.validate_hftoken(hf_token)
|
145
|
-
if not is_valid_token:
|
146
|
-
logger.error(
|
147
|
-
"Invalid Hugging Face token provided in the config file, this might cause issues with downloading the restricted model checkpoints."
|
148
|
-
)
|
149
|
-
logger.info("Continuing without Hugging Face token")
|
150
|
-
|
151
|
-
@property
|
152
|
-
def client(self):
|
153
|
-
if self._client is None:
|
154
|
-
assert "model" in self.config, "model info not found in the config file"
|
155
|
-
model = self.config.get('model')
|
156
|
-
assert "user_id" in model, "user_id not found in the config file"
|
157
|
-
assert "app_id" in model, "app_id not found in the config file"
|
158
|
-
# The owner of the model and the app.
|
159
|
-
user_id = model.get('user_id')
|
160
|
-
app_id = model.get('app_id')
|
161
|
-
|
162
|
-
base = os.environ.get('CLARIFAI_API_BASE', 'https://api.clarifai.com')
|
163
|
-
|
164
|
-
self._client = BaseClient(user_id=user_id, app_id=app_id, base=base)
|
165
|
-
|
166
|
-
return self._client
|
167
|
-
|
168
|
-
@property
|
169
|
-
def model_url(self):
|
170
|
-
url_helper = ClarifaiUrlHelper(self._client.auth_helper)
|
171
|
-
if self.model_version_id is not None:
|
172
|
-
return url_helper.clarifai_url(self.client.user_app_id.user_id,
|
173
|
-
self.client.user_app_id.app_id, "models", self.model_id)
|
174
|
-
else:
|
175
|
-
return url_helper.clarifai_url(self.client.user_app_id.user_id,
|
176
|
-
self.client.user_app_id.app_id, "models", self.model_id,
|
177
|
-
self.model_version_id)
|
178
|
-
|
179
|
-
def _get_model_proto(self):
|
180
|
-
assert "model" in self.config, "model info not found in the config file"
|
181
|
-
model = self.config.get('model')
|
182
|
-
|
183
|
-
assert "model_type_id" in model, "model_type_id not found in the config file"
|
184
|
-
assert "id" in model, "model_id not found in the config file"
|
185
|
-
assert "user_id" in model, "user_id not found in the config file"
|
186
|
-
assert "app_id" in model, "app_id not found in the config file"
|
187
|
-
|
188
|
-
model_proto = json_format.ParseDict(model, resources_pb2.Model())
|
189
|
-
|
190
|
-
return model_proto
|
191
|
-
|
192
|
-
def _get_inference_compute_info(self):
|
193
|
-
assert ("inference_compute_info" in self.config
|
194
|
-
), "inference_compute_info not found in the config file"
|
195
|
-
inference_compute_info = self.config.get('inference_compute_info')
|
196
|
-
return json_format.ParseDict(inference_compute_info, resources_pb2.ComputeInfo())
|
197
|
-
|
198
|
-
def check_model_exists(self):
|
199
|
-
resp = self.client.STUB.GetModel(
|
200
|
-
service_pb2.GetModelRequest(
|
201
|
-
user_app_id=self.client.user_app_id, model_id=self.model_proto.id))
|
202
|
-
if resp.status.code == status_code_pb2.SUCCESS:
|
203
|
-
return True
|
204
|
-
return False
|
205
|
-
|
206
|
-
def maybe_create_model(self):
|
207
|
-
if self.check_model_exists():
|
208
|
-
logger.info(
|
209
|
-
f"Model '{self.client.user_app_id.user_id}/{self.client.user_app_id.app_id}/models/{self.model_proto.id}' already exists, "
|
210
|
-
f"will create a new version for it.")
|
211
|
-
return
|
212
|
-
|
213
|
-
request = service_pb2.PostModelsRequest(
|
214
|
-
user_app_id=self.client.user_app_id,
|
215
|
-
models=[self.model_proto],
|
216
|
-
)
|
217
|
-
return self.client.STUB.PostModels(request)
|
218
|
-
|
219
|
-
def _parse_requirements(self):
|
220
|
-
# parse the user's requirements.txt to determine the proper base image to build on top of, based on the torch and other large dependencies and it's versions
|
221
|
-
# List of dependencies to look for
|
222
|
-
dependencies = [
|
223
|
-
'torch',
|
224
|
-
]
|
225
|
-
# Escape dependency names for regex
|
226
|
-
dep_pattern = '|'.join(map(re.escape, dependencies))
|
227
|
-
# All possible version specifiers
|
228
|
-
version_specifiers = '==|>=|<=|!=|~=|>|<'
|
229
|
-
# Compile a regex pattern with verbose mode for readability
|
230
|
-
pattern = re.compile(r"""
|
231
|
-
^\s* # Start of line, optional whitespace
|
232
|
-
(?P<dependency>""" + dep_pattern + r""") # Dependency name
|
233
|
-
\s* # Optional whitespace
|
234
|
-
(?P<specifier>""" + version_specifiers + r""")? # Optional version specifier
|
235
|
-
\s* # Optional whitespace
|
236
|
-
(?P<version>[^\s;]+)? # Optional version (up to space or semicolon)
|
237
|
-
""", re.VERBOSE)
|
238
|
-
|
239
|
-
deendencies_version = {}
|
240
|
-
with open(os.path.join(self.folder, 'requirements.txt'), 'r') as file:
|
241
|
-
for line in file:
|
242
|
-
# Skip empty lines and comments
|
243
|
-
line = line.strip()
|
244
|
-
if not line or line.startswith('#'):
|
245
|
-
continue
|
246
|
-
match = pattern.match(line)
|
247
|
-
if match:
|
248
|
-
dependency = match.group('dependency')
|
249
|
-
version = match.group('version')
|
250
|
-
if dependency == "torch" and line.find(
|
251
|
-
'whl/cpu') > 0: # Ignore torch-cpu whl files, use base mage.
|
252
|
-
continue
|
253
|
-
|
254
|
-
deendencies_version[dependency] = version if version else None
|
255
|
-
return deendencies_version
|
256
|
-
|
257
|
-
def create_dockerfile(self):
|
258
|
-
dockerfile_template = os.path.join(
|
259
|
-
os.path.dirname(os.path.dirname(__file__)),
|
260
|
-
'dockerfile_template',
|
261
|
-
'Dockerfile.template',
|
262
|
-
)
|
263
|
-
|
264
|
-
with open(dockerfile_template, 'r') as template_file:
|
265
|
-
dockerfile_template = template_file.read()
|
266
|
-
|
267
|
-
dockerfile_template = Template(dockerfile_template)
|
268
|
-
|
269
|
-
# Get the Python version from the config file
|
270
|
-
build_info = self.config.get('build_info', {})
|
271
|
-
if 'python_version' in build_info:
|
272
|
-
python_version = build_info['python_version']
|
273
|
-
if python_version not in AVAILABLE_PYTHON_IMAGES:
|
274
|
-
logger.error(
|
275
|
-
f"Python version {python_version} not supported, please use one of the following versions: {AVAILABLE_PYTHON_IMAGES}"
|
276
|
-
)
|
277
|
-
return
|
278
|
-
logger.info(
|
279
|
-
f"Using Python version {python_version} from the config file to build the Dockerfile")
|
280
|
-
else:
|
281
|
-
logger.info(
|
282
|
-
f"Python version not found in the config file, using default Python version: {DEFAULT_PYTHON_VERSION}"
|
283
|
-
)
|
284
|
-
python_version = DEFAULT_PYTHON_VERSION
|
285
|
-
|
286
|
-
# This is always the final image used for runtime.
|
287
|
-
runtime_image = PYTHON_RUNTIME_IMAGE.format(python_version=python_version)
|
288
|
-
builder_image = PYTHON_BUILDER_IMAGE.format(python_version=python_version)
|
289
|
-
downloader_image = PYTHON_BUILDER_IMAGE.format(python_version=python_version)
|
290
|
-
|
291
|
-
# Parse the requirements.txt file to determine the base image
|
292
|
-
dependencies = self._parse_requirements()
|
293
|
-
if 'torch' in dependencies and dependencies['torch']:
|
294
|
-
torch_version = dependencies['torch']
|
295
|
-
|
296
|
-
# Sort in reverse so that newer cuda versions come first and are preferred.
|
297
|
-
for image in sorted(AVAILABLE_TORCH_IMAGES, reverse=True):
|
298
|
-
if torch_version in image and f'py{python_version}' in image:
|
299
|
-
cuda_version = image.split('-')[-1].replace('cuda', '')
|
300
|
-
builder_image = TORCH_BASE_IMAGE.format(
|
301
|
-
torch_version=torch_version,
|
302
|
-
python_version=python_version,
|
303
|
-
cuda_version=cuda_version,
|
304
|
-
)
|
305
|
-
# download_image = base_image
|
306
|
-
logger.info(f"Using Torch version {torch_version} base image to build the Docker image")
|
307
|
-
break
|
308
|
-
# else: # if not torch then use the download image for the base image too
|
309
|
-
# # base_image = download_image
|
310
|
-
# requirements_image = base_image
|
311
|
-
# Replace placeholders with actual values
|
312
|
-
dockerfile_content = dockerfile_template.safe_substitute(
|
313
|
-
name='main',
|
314
|
-
BUILDER_IMAGE=builder_image, # for pip requirements
|
315
|
-
RUNTIME_IMAGE=runtime_image, # for runtime
|
316
|
-
DOWNLOADER_IMAGE=downloader_image, # for downloading checkpoints
|
317
|
-
)
|
318
|
-
|
319
|
-
# Write Dockerfile
|
320
|
-
with open(os.path.join(self.folder, 'Dockerfile'), 'w') as dockerfile:
|
321
|
-
dockerfile.write(dockerfile_content)
|
322
|
-
|
323
|
-
@property
|
324
|
-
def checkpoint_path(self):
|
325
|
-
return self._checkpoint_path(self.folder)
|
326
|
-
|
327
|
-
def _checkpoint_path(self, folder):
|
328
|
-
return os.path.join(folder, self.checkpoint_suffix)
|
329
|
-
|
330
|
-
@property
|
331
|
-
def checkpoint_suffix(self):
|
332
|
-
return '1/checkpoints'
|
333
|
-
|
334
|
-
@property
|
335
|
-
def tar_file(self):
|
336
|
-
return f"{self.folder}.tar.gz"
|
337
|
-
|
338
|
-
def download_checkpoints(self, checkpoint_path_override: str = None):
|
339
|
-
"""
|
340
|
-
Downloads the checkpoints specified in the config file.
|
341
|
-
|
342
|
-
:param checkpoint_path_override: The path to download the checkpoints to. If not provided, the
|
343
|
-
default path is used based on the folder ModelUploader was initialized with. The
|
344
|
-
checkpoint_suffix will be appended to the path.
|
345
|
-
"""
|
346
|
-
if not self.config.get("checkpoints"):
|
347
|
-
logger.info("No checkpoints specified in the config file")
|
348
|
-
return True
|
349
|
-
|
350
|
-
loader_type, repo_id, hf_token = self._validate_config_checkpoints()
|
351
|
-
|
352
|
-
success = True
|
353
|
-
if loader_type == "huggingface":
|
354
|
-
loader = HuggingFaceLoader(repo_id=repo_id, token=hf_token)
|
355
|
-
path = self._checkpoint_path(
|
356
|
-
checkpoint_path_override) if checkpoint_path_override else self.checkpoint_path
|
357
|
-
success = loader.download_checkpoints(path)
|
358
|
-
|
359
|
-
if loader_type:
|
360
|
-
if not success:
|
361
|
-
logger.error(f"Failed to download checkpoints for model {repo_id}")
|
362
|
-
sys.exit(1)
|
363
|
-
else:
|
364
|
-
logger.info(f"Downloaded checkpoints for model {repo_id}")
|
365
|
-
return success
|
366
|
-
|
367
|
-
def _concepts_protos_from_concepts(self, concepts):
|
368
|
-
concept_protos = []
|
369
|
-
for concept in concepts:
|
370
|
-
concept_protos.append(resources_pb2.Concept(
|
371
|
-
id=str(concept[0]),
|
372
|
-
name=concept[1],
|
373
|
-
))
|
374
|
-
return concept_protos
|
375
|
-
|
376
|
-
def hf_labels_to_config(self, labels, config_file):
|
377
|
-
with open(config_file, 'r') as file:
|
378
|
-
config = yaml.safe_load(file)
|
379
|
-
model = config.get('model')
|
380
|
-
model_type_id = model.get('model_type_id')
|
381
|
-
assert model_type_id in CONCEPTS_REQUIRED_MODEL_TYPE, f"Model type {model_type_id} not supported for concepts"
|
382
|
-
concept_protos = self._concepts_protos_from_concepts(labels)
|
383
|
-
|
384
|
-
config['concepts'] = [{'id': concept.id, 'name': concept.name} for concept in concept_protos]
|
385
|
-
|
386
|
-
with open(config_file, 'w') as file:
|
387
|
-
yaml.dump(config, file, sort_keys=False)
|
388
|
-
concepts = config.get('concepts')
|
389
|
-
logger.info(f"Updated config.yaml with {len(concepts)} concepts.")
|
390
|
-
|
391
|
-
def get_model_version_proto(self):
|
392
|
-
|
393
|
-
model_version_proto = resources_pb2.ModelVersion(
|
394
|
-
pretrained_model_config=resources_pb2.PretrainedModelConfig(),
|
395
|
-
inference_compute_info=self.inference_compute_info,
|
396
|
-
)
|
397
|
-
|
398
|
-
model_type_id = self.config.get('model').get('model_type_id')
|
399
|
-
if model_type_id in CONCEPTS_REQUIRED_MODEL_TYPE:
|
400
|
-
|
401
|
-
if 'concepts' in self.config:
|
402
|
-
labels = self.config.get('concepts')
|
403
|
-
logger.info(f"Found {len(labels)} concepts in the config file.")
|
404
|
-
for concept in labels:
|
405
|
-
concept_proto = json_format.ParseDict(concept, resources_pb2.Concept())
|
406
|
-
model_version_proto.output_info.data.concepts.append(concept_proto)
|
407
|
-
else:
|
408
|
-
labels = HuggingFaceLoader.fetch_labels(self.checkpoint_path)
|
409
|
-
logger.info(f"Found {len(labels)} concepts from the model checkpoints.")
|
410
|
-
# sort the concepts by id and then update the config file
|
411
|
-
labels = sorted(labels.items(), key=lambda x: int(x[0]))
|
412
|
-
|
413
|
-
config_file = os.path.join(self.folder, 'config.yaml')
|
414
|
-
try:
|
415
|
-
self.hf_labels_to_config(labels, config_file)
|
416
|
-
except Exception as e:
|
417
|
-
logger.error(f"Failed to update the config.yaml file with the concepts: {e}")
|
418
|
-
|
419
|
-
model_version_proto.output_info.data.concepts.extend(
|
420
|
-
self._concepts_protos_from_concepts(labels))
|
421
|
-
return model_version_proto
|
422
|
-
|
423
|
-
def upload_model_version(self, download_checkpoints):
|
424
|
-
file_path = f"{self.folder}.tar.gz"
|
425
|
-
logger.info(f"Will tar it into file: {file_path}")
|
426
|
-
|
427
|
-
model_type_id = self.config.get('model').get('model_type_id')
|
428
|
-
|
429
|
-
if (model_type_id in CONCEPTS_REQUIRED_MODEL_TYPE) and 'concepts' not in self.config:
|
430
|
-
logger.info(
|
431
|
-
f"Model type {model_type_id} requires concepts to be specified in the config.yaml file.."
|
432
|
-
)
|
433
|
-
if self.config.get("checkpoints"):
|
434
|
-
logger.info(
|
435
|
-
"Checkpoints specified in the config.yaml file, will download the HF model's config.json file to infer the concepts."
|
436
|
-
)
|
437
|
-
|
438
|
-
if not download_checkpoints and not HuggingFaceLoader.validate_config(
|
439
|
-
self.checkpoint_path):
|
440
|
-
|
441
|
-
input(
|
442
|
-
"Press Enter to download the HuggingFace model's config.json file to infer the concepts and continue..."
|
443
|
-
)
|
444
|
-
loader_type, repo_id, hf_token = self._validate_config_checkpoints()
|
445
|
-
if loader_type == "huggingface":
|
446
|
-
loader = HuggingFaceLoader(repo_id=repo_id, token=hf_token)
|
447
|
-
loader.download_config(self.checkpoint_path)
|
448
|
-
|
449
|
-
else:
|
450
|
-
logger.error(
|
451
|
-
"No checkpoints specified in the config.yaml file to infer the concepts. Please either specify the concepts directly in the config.yaml file or include a checkpoints section to download the HF model's config.json file to infer the concepts."
|
452
|
-
)
|
453
|
-
return
|
454
|
-
|
455
|
-
model_version_proto = self.get_model_version_proto()
|
456
|
-
|
457
|
-
def filter_func(tarinfo):
|
458
|
-
name = tarinfo.name
|
459
|
-
exclude = [self.tar_file, "*~"]
|
460
|
-
if not download_checkpoints:
|
461
|
-
exclude.append(self.checkpoint_suffix)
|
462
|
-
return None if any(name.endswith(ex) for ex in exclude) else tarinfo
|
463
|
-
|
464
|
-
with tarfile.open(self.tar_file, "w:gz") as tar:
|
465
|
-
tar.add(self.folder, arcname=".", filter=filter_func)
|
466
|
-
logger.info("Tarring complete, about to start upload.")
|
467
|
-
|
468
|
-
file_size = os.path.getsize(self.tar_file)
|
469
|
-
logger.info(f"Size of the tar is: {file_size} bytes")
|
470
|
-
|
471
|
-
self.maybe_create_model()
|
472
|
-
if not self.check_model_exists():
|
473
|
-
logger.error(f"Failed to create model: {self.model_proto.id}")
|
474
|
-
sys.exit(1)
|
475
|
-
|
476
|
-
for response in self.client.STUB.PostModelVersionsUpload(
|
477
|
-
self.model_version_stream_upload_iterator(model_version_proto, file_path),):
|
478
|
-
percent_completed = 0
|
479
|
-
if response.status.code == status_code_pb2.UPLOAD_IN_PROGRESS:
|
480
|
-
percent_completed = response.status.percent_completed
|
481
|
-
details = response.status.details
|
482
|
-
|
483
|
-
_clear_line()
|
484
|
-
print(
|
485
|
-
f"Status: {response.status.description}, "
|
486
|
-
f"Progress: {percent_completed}% - {details} ",
|
487
|
-
f"request_id: {response.status.req_id}",
|
488
|
-
end='\r',
|
489
|
-
flush=True)
|
490
|
-
logger.info("")
|
491
|
-
if response.status.code != status_code_pb2.MODEL_BUILDING:
|
492
|
-
logger.error(f"Failed to upload model version: {response}")
|
493
|
-
return
|
494
|
-
self.model_version_id = response.model_version_id
|
495
|
-
logger.info(f"Created Model Version ID: {self.model_version_id}")
|
496
|
-
logger.info(f"Full url to that version is: {self.model_url}")
|
497
|
-
try:
|
498
|
-
self.monitor_model_build()
|
499
|
-
finally:
|
500
|
-
if os.path.exists(self.tar_file):
|
501
|
-
logger.info(f"Cleaning up upload file: {self.tar_file}")
|
502
|
-
os.remove(self.tar_file)
|
503
|
-
|
504
|
-
def model_version_stream_upload_iterator(self, model_version_proto, file_path):
|
505
|
-
yield self.init_upload_model_version(model_version_proto, file_path)
|
506
|
-
with open(file_path, "rb") as f:
|
507
|
-
file_size = os.path.getsize(file_path)
|
508
|
-
chunk_size = int(127 * 1024 * 1024) # 127MB chunk size
|
509
|
-
num_chunks = (file_size // chunk_size) + 1
|
510
|
-
logger.info("Uploading file...")
|
511
|
-
logger.info(f"File size: {file_size}")
|
512
|
-
logger.info(f"Chunk size: {chunk_size}")
|
513
|
-
logger.info(f"Number of chunks: {num_chunks}")
|
514
|
-
read_so_far = 0
|
515
|
-
for part_id in range(num_chunks):
|
516
|
-
try:
|
517
|
-
chunk_size = min(chunk_size, file_size - read_so_far)
|
518
|
-
chunk = f.read(chunk_size)
|
519
|
-
if not chunk:
|
520
|
-
break
|
521
|
-
read_so_far += len(chunk)
|
522
|
-
yield service_pb2.PostModelVersionsUploadRequest(
|
523
|
-
content_part=resources_pb2.UploadContentPart(
|
524
|
-
data=chunk,
|
525
|
-
part_number=part_id + 1,
|
526
|
-
range_start=read_so_far,
|
527
|
-
))
|
528
|
-
except Exception as e:
|
529
|
-
logger.exception(f"\nError uploading file: {e}")
|
530
|
-
break
|
531
|
-
|
532
|
-
if read_so_far == file_size:
|
533
|
-
logger.info("\nUpload complete!, waiting for model build...")
|
534
|
-
|
535
|
-
def init_upload_model_version(self, model_version_proto, file_path):
|
536
|
-
file_size = os.path.getsize(file_path)
|
537
|
-
logger.info(f"Uploading model version of model {self.model_proto.id}")
|
538
|
-
logger.info(f"Using file '{os.path.basename(file_path)}' of size: {file_size} bytes")
|
539
|
-
result = service_pb2.PostModelVersionsUploadRequest(
|
540
|
-
upload_config=service_pb2.PostModelVersionsUploadConfig(
|
541
|
-
user_app_id=self.client.user_app_id,
|
542
|
-
model_id=self.model_proto.id,
|
543
|
-
model_version=model_version_proto,
|
544
|
-
total_size=file_size,
|
545
|
-
is_v3=self.is_v3,
|
546
|
-
))
|
547
|
-
return result
|
548
|
-
|
549
|
-
def get_model_build_logs(self):
|
550
|
-
logs_request = service_pb2.ListLogEntriesRequest(
|
551
|
-
log_type="builder",
|
552
|
-
user_app_id=self.client.user_app_id,
|
553
|
-
model_id=self.model_proto.id,
|
554
|
-
model_version_id=self.model_version_id,
|
555
|
-
page=1,
|
556
|
-
per_page=50)
|
557
|
-
response = self.client.STUB.ListLogEntries(logs_request)
|
558
|
-
|
559
|
-
return response
|
560
|
-
|
561
|
-
def monitor_model_build(self):
|
562
|
-
st = time.time()
|
563
|
-
seen_logs = set() # To avoid duplicate log messages
|
564
|
-
while True:
|
565
|
-
resp = self.client.STUB.GetModelVersion(
|
566
|
-
service_pb2.GetModelVersionRequest(
|
567
|
-
user_app_id=self.client.user_app_id,
|
568
|
-
model_id=self.model_proto.id,
|
569
|
-
version_id=self.model_version_id,
|
570
|
-
))
|
571
|
-
status_code = resp.model_version.status.code
|
572
|
-
if status_code == status_code_pb2.MODEL_BUILDING:
|
573
|
-
print(f"Model is building... (elapsed {time.time() - st:.1f}s)", end='\r', flush=True)
|
574
|
-
|
575
|
-
# Fetch and display the logs
|
576
|
-
logs = self.get_model_build_logs()
|
577
|
-
for log_entry in logs.log_entries:
|
578
|
-
if log_entry.url not in seen_logs:
|
579
|
-
seen_logs.add(log_entry.url)
|
580
|
-
logger.info(f"{escape(log_entry.message.strip())}")
|
581
|
-
time.sleep(1)
|
582
|
-
elif status_code == status_code_pb2.MODEL_TRAINED:
|
583
|
-
logger.info(f"\nModel build complete! (elapsed {time.time() - st:.1f}s)")
|
584
|
-
logger.info(f"Check out the model at {self.model_url} version: {self.model_version_id}")
|
585
|
-
return True
|
586
|
-
else:
|
587
|
-
logger.info(
|
588
|
-
f"\nModel build failed with status: {resp.model_version.status} and response {resp}")
|
589
|
-
return False
|
590
|
-
|
591
|
-
|
592
|
-
def main(folder, download_checkpoints, skip_dockerfile):
|
593
|
-
uploader = ModelUploader(folder)
|
594
|
-
if download_checkpoints:
|
595
|
-
uploader.download_checkpoints()
|
596
|
-
if not skip_dockerfile:
|
597
|
-
uploader.create_dockerfile()
|
598
|
-
exists = uploader.check_model_exists()
|
599
|
-
if exists:
|
600
|
-
logger.info(
|
601
|
-
f"Model already exists at {uploader.model_url}, this upload will create a new version for it."
|
602
|
-
)
|
603
|
-
else:
|
604
|
-
logger.info(f"New model will be created at {uploader.model_url} with it's first version.")
|
605
|
-
|
606
|
-
input("Press Enter to continue...")
|
607
|
-
uploader.upload_model_version(download_checkpoints)
|
@@ -1,30 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
|
3
|
-
registry = os.environ.get('CLARIFAI_BASE_IMAGE_REGISTRY', 'public.ecr.aws/clarifai-models')
|
4
|
-
|
5
|
-
GIT_SHA = "df565436eea93efb3e8d1eb558a0a46df29523ec"
|
6
|
-
|
7
|
-
PYTHON_BASE_IMAGE = registry + '/python-base:{python_version}-' + GIT_SHA
|
8
|
-
TORCH_BASE_IMAGE = registry + '/torch:{torch_version}-py{python_version}-cuda{cuda_version}-' + GIT_SHA
|
9
|
-
|
10
|
-
# List of available python base images
|
11
|
-
AVAILABLE_PYTHON_IMAGES = ['3.11', '3.12']
|
12
|
-
|
13
|
-
DEFAULT_PYTHON_VERSION = 3.12
|
14
|
-
|
15
|
-
# By default we download at runtime.
|
16
|
-
DEFAULT_DOWNLOAD_CHECKPOINT_WHEN = "runtime"
|
17
|
-
|
18
|
-
# List of available torch images
|
19
|
-
# Keep sorted by most recent cuda version.
|
20
|
-
AVAILABLE_TORCH_IMAGES = [
|
21
|
-
'2.4.1-py3.11-cuda124',
|
22
|
-
'2.5.1-py3.11-cuda124',
|
23
|
-
'2.4.1-py3.12-cuda124',
|
24
|
-
'2.5.1-py3.12-cuda124',
|
25
|
-
# '2.4.1-py3.13-cuda124',
|
26
|
-
# '2.5.1-py3.13-cuda124',
|
27
|
-
]
|
28
|
-
CONCEPTS_REQUIRED_MODEL_TYPE = [
|
29
|
-
'visual-classifier', 'visual-detector', 'visual-segmenter', 'text-classifier'
|
30
|
-
]
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|