Skip to content

Commit 2eecf55

Browse files
author
David Eigen
committed
Merge branch 'master' into video-inference
2 parents 9ac9293 + 70fb5ad commit 2eecf55

File tree

11 files changed

+136
-51
lines changed

11 files changed

+136
-51
lines changed

CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,22 @@
1+
## [[11.1.1]](https://github.com/Clarifai/clarifai-python/releases/tag/11.1.1) - [PyPI](https://pypi.org/project/clarifai/11.1.1/) - 2025-02-06
2+
3+
### Changed
4+
5+
- Don't validate API in server.py [(#509)] (https://github.com/Clarifai/clarifai-python/pull/509)
6+
7+
## [[11.1.0]](https://github.com/Clarifai/clarifai-python/releases/tag/11.1.0) - [PyPI](https://pypi.org/project/clarifai/11.1.0/) - 2025-02-05
8+
9+
### Changed
10+
11+
- Fixed Docker test locally [(#505)] (https://github.com/Clarifai/clarifai-python/pull/505)
12+
- Fixed HF checkpoints error [(#504)] (https://github.com/Clarifai/clarifai-python/pull/504)
13+
- Fixed Deployment Tests [(#502)] (https://github.com/Clarifai/clarifai-python/pull/502)
14+
- Fixed Issue with Filename as Invalid Input ID [(#501)] (https://github.com/Clarifai/clarifai-python/pull/501)
15+
- Update Model Predict CLI [(#500)] (https://github.com/Clarifai/clarifai-python/pull/500)
16+
- Tests Health Port to None [(#499)] (https://github.com/Clarifai/clarifai-python/pull/499)
17+
- Refactor model class and runners to be more independent [(#494)] (https://github.com/Clarifai/clarifai-python/pull/494)
18+
- Add storage request inferred from tar and checkpoint size [(#479)] (https://github.com/Clarifai/clarifai-python/pull/479)
19+
120
## [[11.0.7]](https://github.com/Clarifai/clarifai-python/releases/tag/11.0.7) - [PyPI](https://pypi.org/project/clarifai/11.0.7/) - 2025-01-24
221

322
### Changed

clarifai/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "11.0.7"
1+
__version__ = "11.1.1"

clarifai/cli/model.py

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,6 @@ def run_locally(model_path, port, mode, keep_env, keep_image):
172172
@click.option('--file_path', required=False, help='File path of file for the model to predict')
173173
@click.option('--url', required=False, help='URL to the file for the model to predict')
174174
@click.option('--bytes', required=False, help='Bytes to the file for the model to predict')
175-
@click.option(
176-
'--input_id', required=False, help='Existing input id in the app for the model to predict')
177175
@click.option('--input_type', required=False, help='Type of input')
178176
@click.option(
179177
'-cc_id',
@@ -187,36 +185,28 @@ def run_locally(model_path, port, mode, keep_env, keep_image):
187185
'--inference_params', required=False, default='{}', help='Inference parameters to override')
188186
@click.option('--output_config', required=False, default='{}', help='Output config to override')
189187
@click.pass_context
190-
def predict(ctx, config, model_id, user_id, app_id, model_url, file_path, url, bytes, input_id,
191-
input_type, compute_cluster_id, nodepool_id, deployment_id, inference_params,
192-
output_config):
188+
def predict(ctx, config, model_id, user_id, app_id, model_url, file_path, url, bytes, input_type,
189+
compute_cluster_id, nodepool_id, deployment_id, inference_params, output_config):
193190
"""Predict using the given model"""
194191
import json
195192

196-
from clarifai.client.deployment import Deployment
197-
from clarifai.client.input import Input
198193
from clarifai.client.model import Model
199-
from clarifai.client.nodepool import Nodepool
200194
from clarifai.utils.cli import from_yaml
201195
if config:
202196
config = from_yaml(config)
203-
model_id, user_id, app_id, model_url, file_path, url, bytes, input_id, input_type, compute_cluster_id, nodepool_id, deployment_id, inference_params, output_config = (
197+
model_id, user_id, app_id, model_url, file_path, url, bytes, input_type, compute_cluster_id, nodepool_id, deployment_id, inference_params, output_config = (
204198
config.get(k, v)
205199
for k, v in [('model_id', model_id), ('user_id', user_id), ('app_id', app_id), (
206200
'model_url', model_url), ('file_path', file_path), ('url', url), ('bytes', bytes), (
207-
'input_id',
208-
input_id), ('input_type',
209-
input_type), ('compute_cluster_id',
210-
compute_cluster_id), ('nodepool_id', nodepool_id), (
211-
'deployment_id',
212-
deployment_id), ('inference_params',
213-
inference_params), ('output_config',
214-
output_config)])
201+
'input_type', input_type), ('compute_cluster_id', compute_cluster_id), (
202+
'nodepool_id',
203+
nodepool_id), ('deployment_id',
204+
deployment_id), ('inference_params',
205+
inference_params), ('output_config',
206+
output_config)])
215207
if sum([opt[1] for opt in [(model_id, 1), (user_id, 1), (app_id, 1), (model_url, 3)]
216208
if opt[0]]) != 3:
217209
raise ValueError("Either --model_id & --user_id & --app_id or --model_url must be provided.")
218-
if sum([1 for opt in [file_path, url, bytes, input_id] if opt]) != 1:
219-
raise ValueError("Exactly one of --file_path, --url, --bytes or --input_id must be provided.")
220210
if compute_cluster_id or nodepool_id or deployment_id:
221211
if sum([
222212
opt[1] for opt in [(compute_cluster_id, 0.5), (nodepool_id, 0.5), (deployment_id, 1)]
@@ -266,21 +256,5 @@ def predict(ctx, config, model_id, user_id, app_id, model_url, file_path, url, b
266256
nodepool_id=nodepool_id,
267257
deployment_id=deployment_id,
268258
inference_params=inference_params,
269-
output_config=output_config)
270-
elif input_id:
271-
inputs = [Input.get_input(input_id)]
272-
runner_selector = None
273-
if deployment_id:
274-
runner_selector = Deployment.get_runner_selector(
275-
user_id=ctx.obj['user_id'], deployment_id=deployment_id)
276-
elif compute_cluster_id and nodepool_id:
277-
runner_selector = Nodepool.get_runner_selector(
278-
user_id=ctx.obj['user_id'],
279-
compute_cluster_id=compute_cluster_id,
280-
nodepool_id=nodepool_id)
281-
model_prediction = model.predict(
282-
inputs=inputs,
283-
runner_selector=runner_selector,
284-
inference_params=inference_params,
285-
output_config=output_config)
259+
output_config=output_config) ## TO DO: Add support for input_id
286260
click.echo(model_prediction)

clarifai/client/input.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,24 @@ def get_mask_proto(input_id: str,
572572

573573
return input_mask_proto
574574

575+
def get_input(self, input_id: str) -> Input:
576+
"""Get Input object of input with input_id provided from the app.
577+
578+
Args:
579+
input_id (str): The input ID for the annotation to get.
580+
581+
Returns:
582+
Input: An Input object for the specified input ID.
583+
584+
Example:
585+
>>> from clarifai.client.input import Inputs
586+
>>> input_obj = Inputs(user_id = 'user_id', app_id = 'demo_app')
587+
>>> input_obj.get_input(input_id='demo')
588+
"""
589+
request = service_pb2.GetInputRequest(user_app_id=self.user_app_id, input_id=input_id)
590+
response = self._grpc_request(self.STUB.GetInput, request)
591+
return response.input
592+
575593
def upload_from_url(self,
576594
input_id: str,
577595
image_url: str = None,

clarifai/runners/dockerfile_template/Dockerfile.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ ENV PYTHONPATH=${PYTHONPATH}:/home/nonroot/main \
7373
CLARIFAI_RUNNER_ID=${CLARIFAI_RUNNER_ID} \
7474
CLARIFAI_NODEPOOL_ID=${CLARIFAI_NODEPOOL_ID} \
7575
CLARIFAI_COMPUTE_CLUSTER_ID=${CLARIFAI_COMPUTE_CLUSTER_ID} \
76-
CLARIFAI_API_BASE=${CLARIFAI_API_BASE}
76+
CLARIFAI_API_BASE=${CLARIFAI_API_BASE:-https://api.clarifai.com}
7777

7878
# Finally run the clarifai entrypoint to start the runner loop and local dev server.
7979
# Note(zeiler): we may want to make this a clarifai CLI call.

clarifai/runners/models/model_builder.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def _clear_line(n: int = 1) -> None:
3232

3333

3434
class ModelBuilder:
35+
DEFAULT_CHECKPOINT_SIZE = 50 * 1024**3 # 50 GiB
3536

3637
def __init__(self, folder: str, validate_api_ids: bool = True, download_validation_only=False):
3738
"""
@@ -154,6 +155,9 @@ def _check_app_exists(self):
154155
resp = self.client.STUB.GetApp(service_pb2.GetAppRequest(user_app_id=self.client.user_app_id))
155156
if resp.status.code == status_code_pb2.SUCCESS:
156157
return True
158+
logger.error(
159+
f"Error checking API {self._base_api} for user app {self.client.user_app_id.user_id}/{self.client.user_app_id.app_id}. Error code: {resp.status.code}"
160+
)
157161
return False
158162

159163
def _validate_config_model(self):
@@ -200,6 +204,24 @@ def _validate_config(self):
200204
)
201205
logger.info("Continuing without Hugging Face token")
202206

207+
@staticmethod
208+
def _get_tar_file_content_size(tar_file_path):
209+
"""
210+
Calculates the total size of the contents of a tar file.
211+
212+
Args:
213+
tar_file_path (str): The path to the tar file.
214+
215+
Returns:
216+
int: The total size of the contents in bytes.
217+
"""
218+
total_size = 0
219+
with tarfile.open(tar_file_path, 'r') as tar:
220+
for member in tar:
221+
if member.isfile():
222+
total_size += member.size
223+
return total_size
224+
203225
@property
204226
def client(self):
205227
if self._client is None:
@@ -211,9 +233,8 @@ def client(self):
211233
user_id = model.get('user_id')
212234
app_id = model.get('app_id')
213235

214-
base = os.environ.get('CLARIFAI_API_BASE', 'https://api.clarifai.com')
215-
216-
self._client = BaseClient(user_id=user_id, app_id=app_id, base=base)
236+
self._base_api = os.environ.get('CLARIFAI_API_BASE', 'https://api.clarifai.com')
237+
self._client = BaseClient(user_id=user_id, app_id=app_id, base=self._base_api)
217238

218239
return self._client
219240

@@ -520,6 +541,18 @@ def filter_func(tarinfo):
520541
file_size = os.path.getsize(self.tar_file)
521542
logger.info(f"Size of the tar is: {file_size} bytes")
522543

544+
self.storage_request_size = self._get_tar_file_content_size(file_path)
545+
if not download_checkpoints and self.config.get("checkpoints"):
546+
# Get the checkpoint size to add to the storage request.
547+
# First check for the env variable, then try querying huggingface. If all else fails, use the default.
548+
checkpoint_size = os.environ.get('CHECKPOINT_SIZE_BYTES', 0)
549+
if not checkpoint_size:
550+
_, repo_id, _ = self._validate_config_checkpoints()
551+
checkpoint_size = HuggingFaceLoader.get_huggingface_checkpoint_total_size(repo_id)
552+
if not checkpoint_size:
553+
checkpoint_size = self.DEFAULT_CHECKPOINT_SIZE
554+
self.storage_request_size += checkpoint_size
555+
523556
self.maybe_create_model()
524557
if not self.check_model_exists():
525558
logger.error(f"Failed to create model: {self.model_proto.id}")
@@ -594,6 +627,7 @@ def init_upload_model_version(self, model_version_proto, file_path):
594627
model_id=self.model_proto.id,
595628
model_version=model_version_proto,
596629
total_size=file_size,
630+
storage_request_size=self.storage_request_size,
597631
is_v3=self.is_v3,
598632
))
599633
return result

clarifai/runners/models/model_run_locally.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,9 @@ def build_docker_image(
287287
# Comment out the COPY instruction that copies the current folder
288288
modified_lines = []
289289
for line in lines:
290-
if 'COPY .' in line and '/app/model_dir/main' in line:
290+
if 'COPY' in line and '/home/nonroot/main' in line:
291+
modified_lines.append(f'# {line}')
292+
elif 'download-checkpoints' in line and '/home/nonroot/main' in line:
291293
modified_lines.append(f'# {line}')
292294
else:
293295
modified_lines.append(line)
@@ -338,15 +340,15 @@ def run_docker_container(self,
338340
if self._gpu_is_available():
339341
cmd.extend(["--gpus", "all"])
340342
# Add volume mappings
341-
cmd.extend(["-v", f"{self.model_path}:/app/model_dir/main"])
343+
cmd.extend(["-v", f"{self.model_path}:/home/nonroot/main"])
342344
# Add environment variables
343345
if env_vars:
344346
for key, value in env_vars.items():
345347
cmd.extend(["-e", f"{key}={value}"])
346348
# Add the image name
347349
cmd.append(image_name)
348350
# update the CMD to run the server
349-
cmd.extend(["--model_path", "/app/model_dir/main", "--grpc", "--port", str(port)])
351+
cmd.extend(["--model_path", "/home/nonroot/main", "--grpc", "--port", str(port)])
350352
# Run the container
351353
process = subprocess.Popen(cmd,)
352354
logger.info(
@@ -387,7 +389,7 @@ def test_model_container(self,
387389
# update the entrypoint for testing the model
388390
cmd.extend(["--entrypoint", "python"])
389391
# Add volume mappings
390-
cmd.extend(["-v", f"{self.model_path}:/app/model_dir/main"])
392+
cmd.extend(["-v", f"{self.model_path}:/home/nonroot/main"])
391393
# Add environment variables
392394
if env_vars:
393395
for key, value in env_vars.items():
@@ -397,7 +399,7 @@ def test_model_container(self,
397399
# update the CMD to test the model inside the container
398400
cmd.extend([
399401
"-c",
400-
"from clarifai.runners.models.model_run_locally import ModelRunLocally; ModelRunLocally('/app/model_dir/main')._run_test()"
402+
"from clarifai.runners.models.model_run_locally import ModelRunLocally; ModelRunLocally('/home/nonroot/main')._run_test()"
401403
])
402404
# Run the container
403405
subprocess.check_call(cmd)

clarifai/runners/server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def main():
6868

6969
parsed_args = parser.parse_args()
7070

71-
builder = ModelBuilder(parsed_args.model_path)
71+
builder = ModelBuilder(parsed_args.model_path, download_validation_only=True)
7272

7373
model = builder.create_model_instance()
7474

@@ -101,7 +101,7 @@ def main():
101101
runner_id=os.environ["CLARIFAI_RUNNER_ID"],
102102
nodepool_id=os.environ["CLARIFAI_NODEPOOL_ID"],
103103
compute_cluster_id=os.environ["CLARIFAI_COMPUTE_CLUSTER_ID"],
104-
base_url=os.environ["CLARIFAI_API_BASE"],
104+
base_url=os.environ.get("CLARIFAI_API_BASE", "https://api.clarifai.com"),
105105
num_parallel_polls=int(os.environ.get("CLARIFAI_NUM_THREADS", 1)),
106106
)
107107
runner.start() # start the runner to fetch work from the API.

clarifai/runners/utils/loader.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import os
55
import shutil
66

7+
import requests
8+
79
from clarifai.utils.logging import logger
810

911

@@ -23,14 +25,14 @@ def __init__(self, repo_id=None, token=None):
2325
login(token=token)
2426
logger.info("Hugging Face token validated")
2527
else:
28+
self.token = None
2629
logger.info("Continuing without Hugging Face token")
2730

2831
@classmethod
2932
def validate_hftoken(cls, hf_token: str):
3033
try:
3134
if importlib.util.find_spec("huggingface_hub") is None:
3235
raise ImportError(cls.HF_DOWNLOAD_TEXT)
33-
os.environ['HF_TOKEN'] = hf_token
3436
from huggingface_hub import HfApi
3537

3638
api = HfApi()
@@ -169,3 +171,34 @@ def fetch_labels(checkpoint_path: str):
169171

170172
labels = config['id2label']
171173
return labels
174+
175+
@staticmethod
176+
def get_huggingface_checkpoint_total_size(repo_name):
177+
"""
178+
Fetches the JSON data for a Hugging Face model using the API with `?blobs=true`.
179+
Calculates the total size from the JSON output.
180+
181+
Args:
182+
repo_name (str): The name of the model on Hugging Face Hub. e.g. "casperhansen/llama-3-8b-instruct-awq"
183+
184+
Returns:
185+
int: The total size in bytes.
186+
"""
187+
try:
188+
url = f"https://huggingface.co/api/models/{repo_name}?blobs=true"
189+
response = requests.get(url)
190+
response.raise_for_status() # Raise an exception for bad status codes
191+
json_data = response.json()
192+
193+
if isinstance(json_data, str):
194+
data = json.loads(json_data)
195+
else:
196+
data = json_data
197+
198+
total_size = 0
199+
for file in data['siblings']:
200+
total_size += file['size']
201+
return total_size
202+
except Exception as e:
203+
logger.error(f"Error fetching checkpoint size from huggingface.co: {e}")
204+
return 0

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
clarifai-grpc>=11.0.5
1+
clarifai-grpc>=11.0.7
22
clarifai-protocol>=0.0.16
33
numpy>=1.22.0
44
tqdm>=4.65.0
@@ -9,3 +9,4 @@ Pillow>=9.5.0
99
tabulate>=0.9.0
1010
fsspec==2024.6.1
1111
click==8.1.7
12+
requests==2.32.3

0 commit comments

Comments
 (0)