clarifai 11.3.0rc2__py3-none-any.whl → 11.4.0__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/__main__.py +1 -1
- clarifai/cli/base.py +144 -136
- clarifai/cli/compute_cluster.py +45 -31
- clarifai/cli/deployment.py +93 -76
- clarifai/cli/model.py +578 -180
- clarifai/cli/nodepool.py +100 -82
- clarifai/client/__init__.py +12 -2
- clarifai/client/app.py +973 -911
- clarifai/client/auth/helper.py +345 -342
- clarifai/client/auth/register.py +7 -7
- clarifai/client/auth/stub.py +107 -106
- clarifai/client/base.py +185 -178
- clarifai/client/compute_cluster.py +214 -180
- clarifai/client/dataset.py +793 -698
- clarifai/client/deployment.py +55 -50
- clarifai/client/input.py +1223 -1088
- clarifai/client/lister.py +47 -45
- clarifai/client/model.py +1939 -1717
- clarifai/client/model_client.py +525 -502
- clarifai/client/module.py +82 -73
- clarifai/client/nodepool.py +358 -213
- clarifai/client/runner.py +58 -0
- clarifai/client/search.py +342 -309
- clarifai/client/user.py +419 -414
- clarifai/client/workflow.py +294 -274
- clarifai/constants/dataset.py +11 -17
- clarifai/constants/model.py +8 -2
- clarifai/datasets/export/inputs_annotations.py +233 -217
- clarifai/datasets/upload/base.py +63 -51
- clarifai/datasets/upload/features.py +43 -38
- clarifai/datasets/upload/image.py +237 -207
- clarifai/datasets/upload/loaders/coco_captions.py +34 -32
- clarifai/datasets/upload/loaders/coco_detection.py +72 -65
- clarifai/datasets/upload/loaders/imagenet_classification.py +57 -53
- clarifai/datasets/upload/loaders/xview_detection.py +274 -132
- clarifai/datasets/upload/multimodal.py +55 -46
- clarifai/datasets/upload/text.py +55 -47
- clarifai/datasets/upload/utils.py +250 -234
- clarifai/errors.py +51 -50
- clarifai/models/api.py +260 -238
- clarifai/modules/css.py +50 -50
- clarifai/modules/pages.py +33 -33
- clarifai/rag/rag.py +312 -288
- clarifai/rag/utils.py +91 -84
- clarifai/runners/models/model_builder.py +906 -802
- clarifai/runners/models/model_class.py +370 -331
- clarifai/runners/models/model_run_locally.py +459 -419
- clarifai/runners/models/model_runner.py +170 -162
- clarifai/runners/models/model_servicer.py +78 -70
- clarifai/runners/server.py +111 -101
- clarifai/runners/utils/code_script.py +225 -187
- clarifai/runners/utils/const.py +4 -1
- clarifai/runners/utils/data_types/__init__.py +12 -0
- clarifai/runners/utils/data_types/data_types.py +598 -0
- clarifai/runners/utils/data_utils.py +387 -440
- clarifai/runners/utils/loader.py +247 -227
- clarifai/runners/utils/method_signatures.py +411 -386
- clarifai/runners/utils/openai_convertor.py +108 -109
- clarifai/runners/utils/serializers.py +175 -179
- clarifai/runners/utils/url_fetcher.py +35 -35
- clarifai/schema/search.py +56 -63
- clarifai/urls/helper.py +125 -102
- clarifai/utils/cli.py +129 -123
- clarifai/utils/config.py +127 -87
- clarifai/utils/constants.py +49 -0
- clarifai/utils/evaluation/helpers.py +503 -466
- clarifai/utils/evaluation/main.py +431 -393
- clarifai/utils/evaluation/testset_annotation_parser.py +154 -144
- clarifai/utils/logging.py +324 -306
- clarifai/utils/misc.py +60 -56
- clarifai/utils/model_train.py +165 -146
- clarifai/utils/protobuf.py +126 -103
- clarifai/versions.py +3 -1
- clarifai/workflows/export.py +48 -50
- clarifai/workflows/utils.py +39 -36
- clarifai/workflows/validate.py +55 -43
- {clarifai-11.3.0rc2.dist-info → clarifai-11.4.0.dist-info}/METADATA +16 -6
- clarifai-11.4.0.dist-info/RECORD +109 -0
- {clarifai-11.3.0rc2.dist-info → clarifai-11.4.0.dist-info}/WHEEL +1 -1
- clarifai/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/__pycache__/__init__.cpython-311.pyc +0 -0
- clarifai/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/__pycache__/errors.cpython-310.pyc +0 -0
- clarifai/__pycache__/errors.cpython-311.pyc +0 -0
- clarifai/__pycache__/versions.cpython-310.pyc +0 -0
- clarifai/__pycache__/versions.cpython-311.pyc +0 -0
- clarifai/cli/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/cli/__pycache__/__init__.cpython-311.pyc +0 -0
- clarifai/cli/__pycache__/base.cpython-310.pyc +0 -0
- clarifai/cli/__pycache__/base.cpython-311.pyc +0 -0
- clarifai/cli/__pycache__/base_cli.cpython-310.pyc +0 -0
- clarifai/cli/__pycache__/compute_cluster.cpython-310.pyc +0 -0
- clarifai/cli/__pycache__/compute_cluster.cpython-311.pyc +0 -0
- clarifai/cli/__pycache__/deployment.cpython-310.pyc +0 -0
- clarifai/cli/__pycache__/deployment.cpython-311.pyc +0 -0
- clarifai/cli/__pycache__/model.cpython-310.pyc +0 -0
- clarifai/cli/__pycache__/model.cpython-311.pyc +0 -0
- clarifai/cli/__pycache__/model_cli.cpython-310.pyc +0 -0
- clarifai/cli/__pycache__/nodepool.cpython-310.pyc +0 -0
- clarifai/cli/__pycache__/nodepool.cpython-311.pyc +0 -0
- clarifai/client/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/__init__.cpython-311.pyc +0 -0
- clarifai/client/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/client/__pycache__/app.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/app.cpython-311.pyc +0 -0
- clarifai/client/__pycache__/app.cpython-39.pyc +0 -0
- clarifai/client/__pycache__/base.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/base.cpython-311.pyc +0 -0
- clarifai/client/__pycache__/compute_cluster.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/compute_cluster.cpython-311.pyc +0 -0
- clarifai/client/__pycache__/dataset.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/dataset.cpython-311.pyc +0 -0
- clarifai/client/__pycache__/deployment.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/deployment.cpython-311.pyc +0 -0
- clarifai/client/__pycache__/input.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/input.cpython-311.pyc +0 -0
- clarifai/client/__pycache__/lister.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/lister.cpython-311.pyc +0 -0
- clarifai/client/__pycache__/model.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/model.cpython-311.pyc +0 -0
- clarifai/client/__pycache__/module.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/module.cpython-311.pyc +0 -0
- clarifai/client/__pycache__/nodepool.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/nodepool.cpython-311.pyc +0 -0
- clarifai/client/__pycache__/search.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/search.cpython-311.pyc +0 -0
- clarifai/client/__pycache__/user.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/user.cpython-311.pyc +0 -0
- clarifai/client/__pycache__/workflow.cpython-310.pyc +0 -0
- clarifai/client/__pycache__/workflow.cpython-311.pyc +0 -0
- clarifai/client/auth/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/client/auth/__pycache__/__init__.cpython-311.pyc +0 -0
- clarifai/client/auth/__pycache__/helper.cpython-310.pyc +0 -0
- clarifai/client/auth/__pycache__/helper.cpython-311.pyc +0 -0
- clarifai/client/auth/__pycache__/register.cpython-310.pyc +0 -0
- clarifai/client/auth/__pycache__/register.cpython-311.pyc +0 -0
- clarifai/client/auth/__pycache__/stub.cpython-310.pyc +0 -0
- clarifai/client/auth/__pycache__/stub.cpython-311.pyc +0 -0
- clarifai/client/cli/__init__.py +0 -0
- clarifai/client/cli/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/client/cli/__pycache__/base_cli.cpython-310.pyc +0 -0
- clarifai/client/cli/__pycache__/model_cli.cpython-310.pyc +0 -0
- clarifai/client/cli/base_cli.py +0 -88
- clarifai/client/cli/model_cli.py +0 -29
- clarifai/constants/__pycache__/base.cpython-310.pyc +0 -0
- clarifai/constants/__pycache__/base.cpython-311.pyc +0 -0
- clarifai/constants/__pycache__/dataset.cpython-310.pyc +0 -0
- clarifai/constants/__pycache__/dataset.cpython-311.pyc +0 -0
- clarifai/constants/__pycache__/input.cpython-310.pyc +0 -0
- clarifai/constants/__pycache__/input.cpython-311.pyc +0 -0
- clarifai/constants/__pycache__/model.cpython-310.pyc +0 -0
- clarifai/constants/__pycache__/model.cpython-311.pyc +0 -0
- clarifai/constants/__pycache__/rag.cpython-310.pyc +0 -0
- clarifai/constants/__pycache__/rag.cpython-311.pyc +0 -0
- clarifai/constants/__pycache__/search.cpython-310.pyc +0 -0
- clarifai/constants/__pycache__/search.cpython-311.pyc +0 -0
- clarifai/constants/__pycache__/workflow.cpython-310.pyc +0 -0
- clarifai/constants/__pycache__/workflow.cpython-311.pyc +0 -0
- clarifai/datasets/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/datasets/__pycache__/__init__.cpython-311.pyc +0 -0
- clarifai/datasets/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/datasets/export/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/datasets/export/__pycache__/__init__.cpython-311.pyc +0 -0
- clarifai/datasets/export/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/datasets/export/__pycache__/inputs_annotations.cpython-310.pyc +0 -0
- clarifai/datasets/export/__pycache__/inputs_annotations.cpython-311.pyc +0 -0
- clarifai/datasets/upload/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/datasets/upload/__pycache__/__init__.cpython-311.pyc +0 -0
- clarifai/datasets/upload/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/datasets/upload/__pycache__/base.cpython-310.pyc +0 -0
- clarifai/datasets/upload/__pycache__/base.cpython-311.pyc +0 -0
- clarifai/datasets/upload/__pycache__/features.cpython-310.pyc +0 -0
- clarifai/datasets/upload/__pycache__/features.cpython-311.pyc +0 -0
- clarifai/datasets/upload/__pycache__/image.cpython-310.pyc +0 -0
- clarifai/datasets/upload/__pycache__/image.cpython-311.pyc +0 -0
- clarifai/datasets/upload/__pycache__/multimodal.cpython-310.pyc +0 -0
- clarifai/datasets/upload/__pycache__/multimodal.cpython-311.pyc +0 -0
- clarifai/datasets/upload/__pycache__/text.cpython-310.pyc +0 -0
- clarifai/datasets/upload/__pycache__/text.cpython-311.pyc +0 -0
- clarifai/datasets/upload/__pycache__/utils.cpython-310.pyc +0 -0
- clarifai/datasets/upload/__pycache__/utils.cpython-311.pyc +0 -0
- clarifai/datasets/upload/loaders/__pycache__/__init__.cpython-311.pyc +0 -0
- clarifai/datasets/upload/loaders/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/datasets/upload/loaders/__pycache__/coco_detection.cpython-311.pyc +0 -0
- clarifai/datasets/upload/loaders/__pycache__/imagenet_classification.cpython-311.pyc +0 -0
- clarifai/models/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/modules/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/rag/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/rag/__pycache__/__init__.cpython-311.pyc +0 -0
- clarifai/rag/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/rag/__pycache__/rag.cpython-310.pyc +0 -0
- clarifai/rag/__pycache__/rag.cpython-311.pyc +0 -0
- clarifai/rag/__pycache__/rag.cpython-39.pyc +0 -0
- clarifai/rag/__pycache__/utils.cpython-310.pyc +0 -0
- clarifai/rag/__pycache__/utils.cpython-311.pyc +0 -0
- clarifai/runners/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/runners/__pycache__/__init__.cpython-311.pyc +0 -0
- clarifai/runners/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/runners/dockerfile_template/Dockerfile.cpu.template +0 -31
- clarifai/runners/dockerfile_template/Dockerfile.cuda.template +0 -42
- clarifai/runners/dockerfile_template/Dockerfile.nim +0 -71
- clarifai/runners/models/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/runners/models/__pycache__/__init__.cpython-311.pyc +0 -0
- clarifai/runners/models/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/runners/models/__pycache__/base_typed_model.cpython-310.pyc +0 -0
- clarifai/runners/models/__pycache__/base_typed_model.cpython-311.pyc +0 -0
- clarifai/runners/models/__pycache__/base_typed_model.cpython-39.pyc +0 -0
- clarifai/runners/models/__pycache__/model_builder.cpython-311.pyc +0 -0
- clarifai/runners/models/__pycache__/model_class.cpython-310.pyc +0 -0
- clarifai/runners/models/__pycache__/model_class.cpython-311.pyc +0 -0
- clarifai/runners/models/__pycache__/model_run_locally.cpython-310-pytest-7.1.2.pyc +0 -0
- clarifai/runners/models/__pycache__/model_run_locally.cpython-310.pyc +0 -0
- clarifai/runners/models/__pycache__/model_run_locally.cpython-311.pyc +0 -0
- clarifai/runners/models/__pycache__/model_runner.cpython-310.pyc +0 -0
- clarifai/runners/models/__pycache__/model_runner.cpython-311.pyc +0 -0
- clarifai/runners/models/__pycache__/model_upload.cpython-310.pyc +0 -0
- clarifai/runners/models/base_typed_model.py +0 -238
- clarifai/runners/models/model_class_refract.py +0 -80
- clarifai/runners/models/model_upload.py +0 -607
- clarifai/runners/models/temp.py +0 -25
- clarifai/runners/utils/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/__init__.cpython-311.pyc +0 -0
- clarifai/runners/utils/__pycache__/__init__.cpython-38.pyc +0 -0
- clarifai/runners/utils/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/runners/utils/__pycache__/buffered_stream.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/buffered_stream.cpython-38.pyc +0 -0
- clarifai/runners/utils/__pycache__/buffered_stream.cpython-39.pyc +0 -0
- clarifai/runners/utils/__pycache__/const.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/const.cpython-311.pyc +0 -0
- clarifai/runners/utils/__pycache__/constants.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/constants.cpython-38.pyc +0 -0
- clarifai/runners/utils/__pycache__/constants.cpython-39.pyc +0 -0
- clarifai/runners/utils/__pycache__/data_handler.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/data_handler.cpython-311.pyc +0 -0
- clarifai/runners/utils/__pycache__/data_handler.cpython-38.pyc +0 -0
- clarifai/runners/utils/__pycache__/data_handler.cpython-39.pyc +0 -0
- clarifai/runners/utils/__pycache__/data_utils.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/data_utils.cpython-311.pyc +0 -0
- clarifai/runners/utils/__pycache__/data_utils.cpython-38.pyc +0 -0
- clarifai/runners/utils/__pycache__/data_utils.cpython-39.pyc +0 -0
- clarifai/runners/utils/__pycache__/grpc_server.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/grpc_server.cpython-38.pyc +0 -0
- clarifai/runners/utils/__pycache__/grpc_server.cpython-39.pyc +0 -0
- clarifai/runners/utils/__pycache__/health.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/health.cpython-38.pyc +0 -0
- clarifai/runners/utils/__pycache__/health.cpython-39.pyc +0 -0
- clarifai/runners/utils/__pycache__/loader.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/loader.cpython-311.pyc +0 -0
- clarifai/runners/utils/__pycache__/logging.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/logging.cpython-38.pyc +0 -0
- clarifai/runners/utils/__pycache__/logging.cpython-39.pyc +0 -0
- clarifai/runners/utils/__pycache__/stream_source.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/stream_source.cpython-39.pyc +0 -0
- clarifai/runners/utils/__pycache__/url_fetcher.cpython-310.pyc +0 -0
- clarifai/runners/utils/__pycache__/url_fetcher.cpython-311.pyc +0 -0
- clarifai/runners/utils/__pycache__/url_fetcher.cpython-38.pyc +0 -0
- clarifai/runners/utils/__pycache__/url_fetcher.cpython-39.pyc +0 -0
- clarifai/runners/utils/data_handler.py +0 -231
- clarifai/runners/utils/data_handler_refract.py +0 -213
- clarifai/runners/utils/data_types.py +0 -469
- clarifai/runners/utils/logger.py +0 -0
- clarifai/runners/utils/openai_format.py +0 -87
- clarifai/schema/__pycache__/search.cpython-310.pyc +0 -0
- clarifai/schema/__pycache__/search.cpython-311.pyc +0 -0
- clarifai/urls/__pycache__/helper.cpython-310.pyc +0 -0
- clarifai/urls/__pycache__/helper.cpython-311.pyc +0 -0
- clarifai/utils/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/utils/__pycache__/__init__.cpython-311.pyc +0 -0
- clarifai/utils/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/utils/__pycache__/cli.cpython-310.pyc +0 -0
- clarifai/utils/__pycache__/cli.cpython-311.pyc +0 -0
- clarifai/utils/__pycache__/config.cpython-311.pyc +0 -0
- clarifai/utils/__pycache__/constants.cpython-310.pyc +0 -0
- clarifai/utils/__pycache__/constants.cpython-311.pyc +0 -0
- clarifai/utils/__pycache__/logging.cpython-310.pyc +0 -0
- clarifai/utils/__pycache__/logging.cpython-311.pyc +0 -0
- clarifai/utils/__pycache__/misc.cpython-310.pyc +0 -0
- clarifai/utils/__pycache__/misc.cpython-311.pyc +0 -0
- clarifai/utils/__pycache__/model_train.cpython-310.pyc +0 -0
- clarifai/utils/__pycache__/model_train.cpython-311.pyc +0 -0
- clarifai/utils/__pycache__/protobuf.cpython-311.pyc +0 -0
- clarifai/utils/evaluation/__pycache__/__init__.cpython-311.pyc +0 -0
- clarifai/utils/evaluation/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/utils/evaluation/__pycache__/helpers.cpython-311.pyc +0 -0
- clarifai/utils/evaluation/__pycache__/main.cpython-311.pyc +0 -0
- clarifai/utils/evaluation/__pycache__/main.cpython-39.pyc +0 -0
- clarifai/workflows/__pycache__/__init__.cpython-310.pyc +0 -0
- clarifai/workflows/__pycache__/__init__.cpython-311.pyc +0 -0
- clarifai/workflows/__pycache__/__init__.cpython-39.pyc +0 -0
- clarifai/workflows/__pycache__/export.cpython-310.pyc +0 -0
- clarifai/workflows/__pycache__/export.cpython-311.pyc +0 -0
- clarifai/workflows/__pycache__/utils.cpython-310.pyc +0 -0
- clarifai/workflows/__pycache__/utils.cpython-311.pyc +0 -0
- clarifai/workflows/__pycache__/validate.cpython-310.pyc +0 -0
- clarifai/workflows/__pycache__/validate.cpython-311.pyc +0 -0
- clarifai-11.3.0rc2.dist-info/RECORD +0 -322
- {clarifai-11.3.0rc2.dist-info → clarifai-11.4.0.dist-info}/entry_points.txt +0 -0
- {clarifai-11.3.0rc2.dist-info → clarifai-11.4.0.dist-info/licenses}/LICENSE +0 -0
- {clarifai-11.3.0rc2.dist-info → clarifai-11.4.0.dist-info}/top_level.txt +0 -0
@@ -11,436 +11,476 @@ import traceback
|
|
11
11
|
import venv
|
12
12
|
|
13
13
|
from clarifai_grpc.grpc.api import resources_pb2, service_pb2
|
14
|
+
|
14
15
|
from clarifai.runners.models.model_builder import ModelBuilder
|
15
16
|
from clarifai.utils.logging import logger
|
16
17
|
|
17
18
|
|
18
19
|
class ModelRunLocally:
|
20
|
+
def __init__(self, model_path):
|
21
|
+
self.model_path = model_path
|
22
|
+
self.requirements_file = os.path.join(self.model_path, "requirements.txt")
|
23
|
+
|
24
|
+
# ModelBuilder contains multiple useful methods to interact with the model
|
25
|
+
self.builder = ModelBuilder(self.model_path, download_validation_only=True)
|
26
|
+
self.config = self.builder.config
|
27
|
+
|
28
|
+
def _requirements_hash(self):
|
29
|
+
"""Generate a hash of the requirements file."""
|
30
|
+
with open(self.requirements_file, "r") as f:
|
31
|
+
return hashlib.md5(f.read().encode('utf-8')).hexdigest()
|
32
|
+
|
33
|
+
def _get_env_executable(self):
|
34
|
+
"""Get the python executable from the virtual environment."""
|
35
|
+
# Depending on the platform, venv scripts are placed in either "Scripts" (Windows) or "bin" (Linux/Mac)
|
36
|
+
if platform.system().lower().startswith("win"):
|
37
|
+
scripts_folder = "Scripts"
|
38
|
+
python_exe = "python.exe"
|
39
|
+
pip_exe = "pip.exe"
|
40
|
+
else:
|
41
|
+
scripts_folder = "bin"
|
42
|
+
python_exe = "python"
|
43
|
+
pip_exe = "pip"
|
19
44
|
|
20
|
-
|
21
|
-
|
22
|
-
self.requirements_file = os.path.join(self.model_path, "requirements.txt")
|
23
|
-
|
24
|
-
# ModelBuilder contains multiple useful methods to interact with the model
|
25
|
-
self.builder = ModelBuilder(self.model_path, download_validation_only=True)
|
26
|
-
self.config = self.builder.config
|
27
|
-
|
28
|
-
def _requirements_hash(self):
|
29
|
-
"""Generate a hash of the requirements file."""
|
30
|
-
with open(self.requirements_file, "r") as f:
|
31
|
-
return hashlib.md5(f.read().encode('utf-8')).hexdigest()
|
32
|
-
|
33
|
-
def _get_env_executable(self):
|
34
|
-
"""Get the python executable from the virtual environment."""
|
35
|
-
# Depending on the platform, venv scripts are placed in either "Scripts" (Windows) or "bin" (Linux/Mac)
|
36
|
-
if platform.system().lower().startswith("win"):
|
37
|
-
scripts_folder = "Scripts"
|
38
|
-
python_exe = "python.exe"
|
39
|
-
pip_exe = "pip.exe"
|
40
|
-
else:
|
41
|
-
scripts_folder = "bin"
|
42
|
-
python_exe = "python"
|
43
|
-
pip_exe = "pip"
|
45
|
+
self.python_executable = os.path.join(self.venv_dir, scripts_folder, python_exe)
|
46
|
+
self.pip_executable = os.path.join(self.venv_dir, scripts_folder, pip_exe)
|
44
47
|
|
45
|
-
|
46
|
-
self.pip_executable = os.path.join(self.venv_dir, scripts_folder, pip_exe)
|
48
|
+
return self.python_executable, self.pip_executable
|
47
49
|
|
48
|
-
|
50
|
+
def create_temp_venv(self):
|
51
|
+
"""Create a temporary virtual environment."""
|
52
|
+
requirements_hash = self._requirements_hash()
|
49
53
|
|
50
|
-
|
51
|
-
|
52
|
-
requirements_hash = self._requirements_hash()
|
54
|
+
temp_dir = os.path.join(tempfile.gettempdir(), str(requirements_hash))
|
55
|
+
venv_dir = os.path.join(temp_dir, "venv")
|
53
56
|
|
54
|
-
|
55
|
-
|
57
|
+
if os.path.exists(temp_dir):
|
58
|
+
logger.info(f"Using previous virtual environment at {temp_dir}")
|
59
|
+
use_existing_venv = True
|
60
|
+
else:
|
61
|
+
logger.info("Creating temporary virtual environment...")
|
62
|
+
use_existing_venv = False
|
63
|
+
venv.create(venv_dir, with_pip=True)
|
64
|
+
logger.info(f"Created temporary virtual environment at {venv_dir}")
|
56
65
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
66
|
+
self.venv_dir = venv_dir
|
67
|
+
self.temp_dir = temp_dir
|
68
|
+
self.python_executable, self.pip_executable = self._get_env_executable()
|
69
|
+
|
70
|
+
return use_existing_venv
|
71
|
+
|
72
|
+
def install_requirements(self):
|
73
|
+
"""Install the dependencies from requirements.txt and Clarifai."""
|
74
|
+
_, pip_executable = self._get_env_executable()
|
75
|
+
try:
|
76
|
+
logger.info(
|
77
|
+
f"Installing requirements from {self.requirements_file}... in the virtual environment {self.venv_dir}"
|
78
|
+
)
|
79
|
+
subprocess.check_call([pip_executable, "install", "-r", self.requirements_file])
|
80
|
+
logger.info("Installing Clarifai package...")
|
81
|
+
subprocess.check_call([pip_executable, "install", "clarifai"])
|
82
|
+
logger.info("Requirements installed successfully!")
|
83
|
+
except subprocess.CalledProcessError as e:
|
84
|
+
logger.error(f"Error installing requirements: {e}")
|
85
|
+
self.clean_up()
|
86
|
+
sys.exit(1)
|
87
|
+
|
88
|
+
def _build_request(self):
|
89
|
+
"""Create a mock inference request for testing the model."""
|
90
|
+
|
91
|
+
model_version_proto = self.builder.get_model_version_proto()
|
92
|
+
model_version_proto.id = "model_version"
|
93
|
+
|
94
|
+
return service_pb2.PostModelOutputsRequest(
|
95
|
+
model=resources_pb2.Model(model_version=model_version_proto),
|
96
|
+
inputs=[
|
97
|
+
resources_pb2.Input(
|
98
|
+
data=resources_pb2.Data(
|
99
|
+
text=resources_pb2.Text(raw="How many people live in new york?"),
|
100
|
+
image=resources_pb2.Image(
|
101
|
+
url="https://samples.clarifai.com/metro-north.jpg"
|
102
|
+
),
|
103
|
+
audio=resources_pb2.Audio(
|
104
|
+
url="https://samples.clarifai.com/GoodMorning.wav"
|
105
|
+
),
|
106
|
+
video=resources_pb2.Video(url="https://samples.clarifai.com/beer.mp4"),
|
107
|
+
)
|
108
|
+
)
|
109
|
+
],
|
110
|
+
)
|
111
|
+
|
112
|
+
def _run_test(self):
|
113
|
+
"""Test the model locally by making a prediction."""
|
114
|
+
# Create the model
|
115
|
+
model = self.builder.create_model_instance()
|
116
|
+
# call its test method, if it has one
|
117
|
+
if hasattr(model, "test"):
|
118
|
+
try:
|
119
|
+
model.test()
|
120
|
+
logger.info("Model tested successfully!")
|
121
|
+
except Exception as e:
|
122
|
+
logger.error(f"Error occurred while testing the model: {e}")
|
123
|
+
traceback.print_exc()
|
124
|
+
|
125
|
+
def test_model(self):
|
126
|
+
"""Test the model by running it locally in the virtual environment."""
|
127
|
+
|
128
|
+
import_path = repr(os.path.dirname(os.path.abspath(__file__)))
|
129
|
+
model_path = repr(self.model_path)
|
130
|
+
|
131
|
+
command_string = (
|
132
|
+
f"import sys; "
|
133
|
+
f"sys.path.append({import_path}); "
|
134
|
+
f"from model_run_locally import ModelRunLocally; "
|
135
|
+
f"ModelRunLocally({model_path})._run_test()"
|
136
|
+
)
|
137
|
+
|
138
|
+
command = [self.python_executable, "-c", command_string]
|
139
|
+
process = None
|
140
|
+
try:
|
141
|
+
logger.info("Testing the model locally...")
|
142
|
+
process = subprocess.Popen(command)
|
143
|
+
# Wait for the process to complete
|
144
|
+
process.wait()
|
145
|
+
if process.returncode == 0:
|
146
|
+
logger.info("Model tested successfully!")
|
147
|
+
if process.returncode != 0:
|
148
|
+
raise subprocess.CalledProcessError(process.returncode, command)
|
149
|
+
except subprocess.CalledProcessError as e:
|
150
|
+
logger.error(f"Error testing the model: {e}")
|
151
|
+
sys.exit(1)
|
152
|
+
except Exception as e:
|
153
|
+
logger.error(f"Unexpected error: {e}")
|
154
|
+
sys.exit(1)
|
155
|
+
finally:
|
156
|
+
# After the function runs, check if the process is still running
|
157
|
+
if process and process.poll() is None:
|
158
|
+
logger.info("Process is still running. Terminating process.")
|
159
|
+
process.terminate()
|
160
|
+
try:
|
161
|
+
process.wait(timeout=5)
|
162
|
+
except subprocess.TimeoutExpired:
|
163
|
+
logger.info("Process did not terminate gracefully. Killing process.")
|
164
|
+
# Kill the process if it doesn't terminate after 5 seconds
|
165
|
+
process.kill()
|
166
|
+
|
167
|
+
# run the model server
|
168
|
+
def run_model_server(self, port=8080):
|
169
|
+
"""Run the Clarifai Runners's model server."""
|
170
|
+
|
171
|
+
command = [
|
172
|
+
self.python_executable,
|
173
|
+
"-m",
|
174
|
+
"clarifai.runners.server",
|
175
|
+
"--model_path",
|
176
|
+
self.model_path,
|
177
|
+
"--grpc",
|
178
|
+
"--port",
|
179
|
+
str(port),
|
180
|
+
]
|
181
|
+
try:
|
182
|
+
logger.info(
|
183
|
+
f"Starting model server at localhost:{port} with the model at {self.model_path}..."
|
184
|
+
)
|
185
|
+
subprocess.check_call(command)
|
186
|
+
logger.info("Model server started successfully and running at localhost:{port}")
|
187
|
+
except subprocess.CalledProcessError as e:
|
188
|
+
logger.error(f"Error running model server: {e}")
|
189
|
+
self.clean_up()
|
190
|
+
sys.exit(1)
|
191
|
+
|
192
|
+
def _docker_hash(self):
|
193
|
+
"""Generate a hash of the combined requirements file and Dockefile"""
|
194
|
+
with open(self.requirements_file, "r") as f:
|
195
|
+
requirements_hash = hashlib.md5(f.read().encode('utf-8')).hexdigest()
|
196
|
+
with open(os.path.join(self.model_path, "Dockerfile"), "r") as f:
|
197
|
+
dockerfile_hash = hashlib.md5(f.read().encode('utf-8')).hexdigest()
|
198
|
+
|
199
|
+
return hashlib.md5(f"{requirements_hash}{dockerfile_hash}".encode('utf-8')).hexdigest()
|
200
|
+
|
201
|
+
def is_docker_installed(self):
|
202
|
+
"""Checks if Docker is installed on the system."""
|
203
|
+
try:
|
204
|
+
logger.info("Checking if Docker is installed...")
|
205
|
+
subprocess.run(["docker", "--version"], check=True)
|
206
|
+
return True
|
207
|
+
except subprocess.CalledProcessError:
|
208
|
+
logger.error(
|
209
|
+
"Docker is not installed! Please install Docker to run the model in a container."
|
210
|
+
)
|
211
|
+
return False
|
212
|
+
|
213
|
+
def build_docker_image(
|
214
|
+
self,
|
215
|
+
image_name="model_image",
|
216
|
+
):
|
217
|
+
"""Build the docker image using the Dockerfile in the model directory."""
|
218
|
+
try:
|
219
|
+
logger.info(f"Building docker image from Dockerfile in {self.model_path}...")
|
220
|
+
|
221
|
+
# since we don't want to copy the model directory into the container, we need to modify the Dockerfile and comment out the COPY instruction
|
222
|
+
dockerfile_path = os.path.join(self.model_path, "Dockerfile")
|
223
|
+
# Read the Dockerfile
|
224
|
+
with open(dockerfile_path, 'r') as file:
|
225
|
+
lines = file.readlines()
|
226
|
+
|
227
|
+
# Comment out the COPY instruction that copies the current folder
|
228
|
+
modified_lines = []
|
229
|
+
for line in lines:
|
230
|
+
if 'COPY' in line and '/home/nonroot/main' in line:
|
231
|
+
modified_lines.append(f'# {line}')
|
232
|
+
elif 'download-checkpoints' in line and '/home/nonroot/main' in line:
|
233
|
+
modified_lines.append(f'# {line}')
|
234
|
+
else:
|
235
|
+
modified_lines.append(line)
|
236
|
+
|
237
|
+
# Create a temporary directory to store the modified Dockerfile
|
238
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
239
|
+
temp_dockerfile_path = os.path.join(temp_dir, "Dockerfile.temp")
|
240
|
+
|
241
|
+
# Write the modified Dockerfile to the temporary file
|
242
|
+
with open(temp_dockerfile_path, 'w') as file:
|
243
|
+
file.writelines(modified_lines)
|
244
|
+
|
245
|
+
# Build the Docker image using the temporary Dockerfile
|
246
|
+
subprocess.check_call(
|
247
|
+
[
|
248
|
+
'docker',
|
249
|
+
'build',
|
250
|
+
'-t',
|
251
|
+
image_name,
|
252
|
+
'-f',
|
253
|
+
temp_dockerfile_path,
|
254
|
+
self.model_path,
|
255
|
+
]
|
256
|
+
)
|
257
|
+
logger.info(f"Docker image '{image_name}' built successfully!")
|
258
|
+
except subprocess.CalledProcessError as e:
|
259
|
+
logger.info(f"Error occurred while building the Docker image: {e}")
|
260
|
+
sys.exit(1)
|
261
|
+
|
262
|
+
def docker_image_exists(self, image_name):
|
263
|
+
"""Check if the Docker image exists."""
|
264
|
+
try:
|
265
|
+
logger.info(f"Checking if Docker image '{image_name}' exists...")
|
266
|
+
subprocess.run(["docker", "inspect", image_name], check=True)
|
267
|
+
logger.info(f"Docker image '{image_name}' exists!")
|
268
|
+
return True
|
269
|
+
except subprocess.CalledProcessError:
|
270
|
+
logger.info(f"Docker image '{image_name}' does not exist!")
|
271
|
+
return False
|
272
|
+
|
273
|
+
def _gpu_is_available(self):
|
274
|
+
"""
|
275
|
+
Checks if nvidia-smi is available, indicating a GPU is likely accessible.
|
276
|
+
"""
|
277
|
+
return shutil.which("nvidia-smi") is not None
|
278
|
+
|
279
|
+
def run_docker_container(
|
280
|
+
self, image_name, container_name="clarifai-model-container", port=8080, env_vars=None
|
281
|
+
):
|
282
|
+
"""Runs a Docker container from the specified image."""
|
283
|
+
try:
|
284
|
+
logger.info(
|
285
|
+
f"Running Docker container '{container_name}' from image '{image_name}'..."
|
286
|
+
)
|
287
|
+
# Base docker run command
|
288
|
+
cmd = ["docker", "run", "--name", container_name, '--rm', "--network", "host"]
|
289
|
+
if self._gpu_is_available():
|
290
|
+
cmd.extend(["--gpus", "all"])
|
291
|
+
# Add volume mappings
|
292
|
+
cmd.extend(["-v", f"{self.model_path}:/home/nonroot/main"])
|
293
|
+
# Add environment variables
|
294
|
+
if env_vars:
|
295
|
+
for key, value in env_vars.items():
|
296
|
+
cmd.extend(["-e", f"{key}={value}"])
|
297
|
+
# Add the image name
|
298
|
+
cmd.append(image_name)
|
299
|
+
# update the CMD to run the server
|
300
|
+
cmd.extend(["--model_path", "/home/nonroot/main", "--grpc", "--port", str(port)])
|
301
|
+
# Run the container
|
302
|
+
process = subprocess.Popen(
|
303
|
+
cmd,
|
304
|
+
)
|
305
|
+
logger.info(
|
306
|
+
f"Docker container '{container_name}' is running successfully! access the model at http://localhost:{port}"
|
307
|
+
)
|
308
|
+
|
309
|
+
# Function to handle Ctrl+C (SIGINT) gracefully
|
310
|
+
def signal_handler(sig, frame):
|
311
|
+
logger.info(f"Stopping Docker container '{container_name}'...")
|
312
|
+
subprocess.run(["docker", "stop", container_name], check=True)
|
313
|
+
process.terminate()
|
314
|
+
logger.info(f"Docker container '{container_name}' stopped successfully!")
|
315
|
+
time.sleep(1)
|
316
|
+
sys.exit(0)
|
317
|
+
|
318
|
+
# Register the signal handler for SIGINT (Ctrl+C)
|
319
|
+
signal.signal(signal.SIGINT, signal_handler)
|
320
|
+
# Wait for the process to finish (keeps the container running until it's stopped)
|
321
|
+
process.wait()
|
322
|
+
except subprocess.CalledProcessError as e:
|
323
|
+
logger.info(f"Error occurred while running the Docker container: {e}")
|
324
|
+
sys.exit(1)
|
325
|
+
except Exception as e:
|
326
|
+
logger.info(f"Error occurred while running the Docker container: {e}")
|
327
|
+
sys.exit(1)
|
328
|
+
|
329
|
+
def test_model_container(
|
330
|
+
self, image_name, container_name="clarifai-model-container", env_vars=None
|
331
|
+
):
|
332
|
+
"""Test the model inside the Docker container."""
|
333
|
+
try:
|
334
|
+
logger.info("Testing the model inside the Docker container...")
|
335
|
+
# Base docker run command
|
336
|
+
cmd = ["docker", "run", "--name", container_name, '--rm', "--network", "host"]
|
337
|
+
if self._gpu_is_available():
|
338
|
+
cmd.extend(["--gpus", "all"])
|
339
|
+
# update the entrypoint for testing the model
|
340
|
+
cmd.extend(["--entrypoint", "python"])
|
341
|
+
# Add volume mappings
|
342
|
+
cmd.extend(["-v", f"{self.model_path}:/home/nonroot/main"])
|
343
|
+
# Add environment variables
|
344
|
+
if env_vars:
|
345
|
+
for key, value in env_vars.items():
|
346
|
+
cmd.extend(["-e", f"{key}={value}"])
|
347
|
+
# Add the image name
|
348
|
+
cmd.append(image_name)
|
349
|
+
# update the CMD to test the model inside the container
|
350
|
+
cmd.extend(
|
351
|
+
[
|
352
|
+
"-c",
|
353
|
+
"from clarifai.runners.models.model_run_locally import ModelRunLocally; ModelRunLocally('/home/nonroot/main')._run_test()",
|
354
|
+
]
|
355
|
+
)
|
356
|
+
# Run the container
|
357
|
+
subprocess.check_call(cmd)
|
358
|
+
logger.info("Model tested successfully!")
|
359
|
+
except subprocess.CalledProcessError as e:
|
360
|
+
logger.error(f"Error testing the model inside the Docker container: {e}")
|
361
|
+
sys.exit(1)
|
362
|
+
|
363
|
+
def container_exists(self, container_name="clarifai-model-container"):
|
364
|
+
"""Check if the Docker container exists."""
|
365
|
+
try:
|
366
|
+
# Run docker ps -a to list all containers (running and stopped)
|
367
|
+
result = subprocess.run(
|
368
|
+
[
|
369
|
+
"docker",
|
370
|
+
"ps",
|
371
|
+
"-a",
|
372
|
+
"--filter",
|
373
|
+
f"name={container_name}",
|
374
|
+
"--format",
|
375
|
+
"{{.Names}}",
|
376
|
+
],
|
377
|
+
check=True,
|
378
|
+
capture_output=True,
|
379
|
+
text=True,
|
380
|
+
)
|
381
|
+
# If the container name is returned, it exists
|
382
|
+
if result.stdout.strip() == container_name:
|
383
|
+
logger.info(f"Docker container '{container_name}' exists.")
|
384
|
+
return True
|
385
|
+
else:
|
386
|
+
return False
|
387
|
+
except subprocess.CalledProcessError as e:
|
388
|
+
logger.error(f"Error occurred while checking if container exists: {e}")
|
389
|
+
return False
|
390
|
+
|
391
|
+
def stop_docker_container(self, container_name="clarifai-model-container"):
|
392
|
+
"""Stop the Docker container if it's running."""
|
393
|
+
try:
|
394
|
+
# Check if the container is running
|
395
|
+
result = subprocess.run(
|
396
|
+
["docker", "ps", "--filter", f"name={container_name}", "--format", "{{.Names}}"],
|
397
|
+
check=True,
|
398
|
+
capture_output=True,
|
399
|
+
text=True,
|
400
|
+
)
|
401
|
+
if result.stdout.strip() == container_name:
|
402
|
+
logger.info(f"Docker container '{container_name}' is running. Stopping it...")
|
403
|
+
subprocess.run(["docker", "stop", container_name], check=True)
|
404
|
+
logger.info(f"Docker container '{container_name}' stopped successfully!")
|
405
|
+
except subprocess.CalledProcessError as e:
|
406
|
+
logger.error(f"Error occurred while stopping the Docker container: {e}")
|
407
|
+
|
408
|
+
def remove_docker_container(self, container_name="clarifai-model-container"):
|
409
|
+
"""Remove the Docker container."""
|
410
|
+
try:
|
411
|
+
logger.info(f"Removing Docker container '{container_name}'...")
|
412
|
+
subprocess.run(["docker", "rm", container_name], check=True)
|
413
|
+
logger.info(f"Docker container '{container_name}' removed successfully!")
|
414
|
+
except subprocess.CalledProcessError as e:
|
415
|
+
logger.error(f"Error occurred while removing the Docker container: {e}")
|
416
|
+
|
417
|
+
def remove_docker_image(self, image_name):
|
418
|
+
"""Remove the Docker image."""
|
419
|
+
try:
|
420
|
+
logger.info(f"Removing Docker image '{image_name}'...")
|
421
|
+
subprocess.run(["docker", "rmi", image_name], check=True)
|
422
|
+
logger.info(f"Docker image '{image_name}' removed successfully!")
|
423
|
+
except subprocess.CalledProcessError as e:
|
424
|
+
logger.error(f"Error occurred while removing the Docker image: {e}")
|
425
|
+
|
426
|
+
def clean_up(self):
|
427
|
+
"""Clean up the temporary virtual environment."""
|
428
|
+
if os.path.exists(self.temp_dir):
|
429
|
+
logger.info("Cleaning up temporary virtual environment...")
|
430
|
+
shutil.rmtree(self.temp_dir)
|
431
|
+
|
432
|
+
|
433
|
+
def main(
|
434
|
+
model_path,
|
435
|
+
run_model_server=False,
|
436
|
+
inside_container=False,
|
437
|
+
port=8080,
|
438
|
+
keep_env=False,
|
439
|
+
keep_image=False,
|
440
|
+
skip_dockerfile: bool = False,
|
441
|
+
):
|
442
|
+
manager = ModelRunLocally(model_path)
|
443
|
+
# get whatever stage is in config.yaml to force download now
|
444
|
+
# also always write to where upload/build wants to, not the /tmp folder that runtime stage uses
|
445
|
+
_, _, _, when, _, _ = manager.builder._validate_config_checkpoints()
|
446
|
+
manager.builder.download_checkpoints(
|
447
|
+
stage=when, checkpoint_path_override=manager.builder.checkpoint_path
|
104
448
|
)
|
449
|
+
if inside_container:
|
450
|
+
if not manager.is_docker_installed():
|
451
|
+
sys.exit(1)
|
452
|
+
if not skip_dockerfile:
|
453
|
+
manager.builder.create_dockerfile()
|
454
|
+
image_tag = manager._docker_hash()
|
455
|
+
model_id = manager.config['model']['id'].lower()
|
456
|
+
# must be in lowercase
|
457
|
+
image_name = f"{model_id}:{image_tag}"
|
458
|
+
container_name = model_id
|
459
|
+
if not manager.docker_image_exists(image_name):
|
460
|
+
manager.build_docker_image(image_name=image_name)
|
461
|
+
try:
|
462
|
+
if run_model_server:
|
463
|
+
manager.run_docker_container(
|
464
|
+
image_name=image_name, container_name=container_name, port=port
|
465
|
+
)
|
466
|
+
else:
|
467
|
+
manager.test_model_container(image_name=image_name, container_name=container_name)
|
468
|
+
finally:
|
469
|
+
if manager.container_exists(container_name):
|
470
|
+
manager.stop_docker_container(container_name)
|
471
|
+
manager.remove_docker_container(container_name=container_name)
|
472
|
+
if not keep_image:
|
473
|
+
manager.remove_docker_image(image_name)
|
105
474
|
|
106
|
-
|
107
|
-
"""Test the model locally by making a prediction."""
|
108
|
-
# Create the model
|
109
|
-
model = self.builder.create_model_instance()
|
110
|
-
# call its test method, if it has one
|
111
|
-
if hasattr(model, "test"):
|
112
|
-
try:
|
113
|
-
model.test()
|
114
|
-
logger.info("Model tested successfully!")
|
115
|
-
except Exception as e:
|
116
|
-
logger.error(f"Error occurred while testing the model: {e}")
|
117
|
-
traceback.print_exc()
|
118
|
-
|
119
|
-
def test_model(self):
|
120
|
-
"""Test the model by running it locally in the virtual environment."""
|
121
|
-
|
122
|
-
import_path = repr(os.path.dirname(os.path.abspath(__file__)))
|
123
|
-
model_path = repr(self.model_path)
|
124
|
-
|
125
|
-
command_string = (f"import sys; "
|
126
|
-
f"sys.path.append({import_path}); "
|
127
|
-
f"from model_run_locally import ModelRunLocally; "
|
128
|
-
f"ModelRunLocally({model_path})._run_test()")
|
129
|
-
|
130
|
-
command = [self.python_executable, "-c", command_string]
|
131
|
-
process = None
|
132
|
-
try:
|
133
|
-
logger.info("Testing the model locally...")
|
134
|
-
process = subprocess.Popen(command)
|
135
|
-
# Wait for the process to complete
|
136
|
-
process.wait()
|
137
|
-
if process.returncode == 0:
|
138
|
-
logger.info("Model tested successfully!")
|
139
|
-
if process.returncode != 0:
|
140
|
-
raise subprocess.CalledProcessError(process.returncode, command)
|
141
|
-
except subprocess.CalledProcessError as e:
|
142
|
-
logger.error(f"Error testing the model: {e}")
|
143
|
-
sys.exit(1)
|
144
|
-
except Exception as e:
|
145
|
-
logger.error(f"Unexpected error: {e}")
|
146
|
-
sys.exit(1)
|
147
|
-
finally:
|
148
|
-
# After the function runs, check if the process is still running
|
149
|
-
if process and process.poll() is None:
|
150
|
-
logger.info("Process is still running. Terminating process.")
|
151
|
-
process.terminate()
|
475
|
+
else:
|
152
476
|
try:
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
command = [
|
164
|
-
self.python_executable, "-m", "clarifai.runners.server", "--model_path", self.model_path,
|
165
|
-
"--grpc", "--port",
|
166
|
-
str(port)
|
167
|
-
]
|
168
|
-
try:
|
169
|
-
logger.info(
|
170
|
-
f"Starting model server at localhost:{port} with the model at {self.model_path}...")
|
171
|
-
subprocess.check_call(command)
|
172
|
-
logger.info("Model server started successfully and running at localhost:{port}")
|
173
|
-
except subprocess.CalledProcessError as e:
|
174
|
-
logger.error(f"Error running model server: {e}")
|
175
|
-
self.clean_up()
|
176
|
-
sys.exit(1)
|
177
|
-
|
178
|
-
def _docker_hash(self):
|
179
|
-
"""Generate a hash of the combined requirements file and Dockefile"""
|
180
|
-
with open(self.requirements_file, "r") as f:
|
181
|
-
requirements_hash = hashlib.md5(f.read().encode('utf-8')).hexdigest()
|
182
|
-
with open(os.path.join(self.model_path, "Dockerfile"), "r") as f:
|
183
|
-
dockerfile_hash = hashlib.md5(f.read().encode('utf-8')).hexdigest()
|
184
|
-
|
185
|
-
return hashlib.md5(f"{requirements_hash}{dockerfile_hash}".encode('utf-8')).hexdigest()
|
186
|
-
|
187
|
-
def is_docker_installed(self):
|
188
|
-
"""Checks if Docker is installed on the system."""
|
189
|
-
try:
|
190
|
-
logger.info("Checking if Docker is installed...")
|
191
|
-
subprocess.run(["docker", "--version"], check=True)
|
192
|
-
return True
|
193
|
-
except subprocess.CalledProcessError:
|
194
|
-
logger.error(
|
195
|
-
"Docker is not installed! Please install Docker to run the model in a container.")
|
196
|
-
return False
|
197
|
-
|
198
|
-
def build_docker_image(
|
199
|
-
self,
|
200
|
-
image_name="model_image",
|
201
|
-
):
|
202
|
-
"""Build the docker image using the Dockerfile in the model directory."""
|
203
|
-
try:
|
204
|
-
logger.info(f"Building docker image from Dockerfile in {self.model_path}...")
|
205
|
-
|
206
|
-
# since we don't want to copy the model directory into the container, we need to modify the Dockerfile and comment out the COPY instruction
|
207
|
-
dockerfile_path = os.path.join(self.model_path, "Dockerfile")
|
208
|
-
# Read the Dockerfile
|
209
|
-
with open(dockerfile_path, 'r') as file:
|
210
|
-
lines = file.readlines()
|
211
|
-
|
212
|
-
# Comment out the COPY instruction that copies the current folder
|
213
|
-
modified_lines = []
|
214
|
-
for line in lines:
|
215
|
-
if 'COPY' in line and '/home/nonroot/main' in line:
|
216
|
-
modified_lines.append(f'# {line}')
|
217
|
-
elif 'download-checkpoints' in line and '/home/nonroot/main' in line:
|
218
|
-
modified_lines.append(f'# {line}')
|
219
|
-
else:
|
220
|
-
modified_lines.append(line)
|
221
|
-
|
222
|
-
# Create a temporary directory to store the modified Dockerfile
|
223
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
224
|
-
temp_dockerfile_path = os.path.join(temp_dir, "Dockerfile.temp")
|
225
|
-
|
226
|
-
# Write the modified Dockerfile to the temporary file
|
227
|
-
with open(temp_dockerfile_path, 'w') as file:
|
228
|
-
file.writelines(modified_lines)
|
229
|
-
|
230
|
-
# Build the Docker image using the temporary Dockerfile
|
231
|
-
subprocess.check_call(
|
232
|
-
['docker', 'build', '-t', image_name, '-f', temp_dockerfile_path, self.model_path])
|
233
|
-
logger.info(f"Docker image '{image_name}' built successfully!")
|
234
|
-
except subprocess.CalledProcessError as e:
|
235
|
-
logger.info(f"Error occurred while building the Docker image: {e}")
|
236
|
-
sys.exit(1)
|
237
|
-
|
238
|
-
def docker_image_exists(self, image_name):
|
239
|
-
"""Check if the Docker image exists."""
|
240
|
-
try:
|
241
|
-
logger.info(f"Checking if Docker image '{image_name}' exists...")
|
242
|
-
subprocess.run(["docker", "inspect", image_name], check=True)
|
243
|
-
logger.info(f"Docker image '{image_name}' exists!")
|
244
|
-
return True
|
245
|
-
except subprocess.CalledProcessError:
|
246
|
-
logger.info(f"Docker image '{image_name}' does not exist!")
|
247
|
-
return False
|
248
|
-
|
249
|
-
def _gpu_is_available(self):
|
250
|
-
"""
|
251
|
-
Checks if nvidia-smi is available, indicating a GPU is likely accessible.
|
252
|
-
"""
|
253
|
-
return shutil.which("nvidia-smi") is not None
|
254
|
-
|
255
|
-
def run_docker_container(self,
|
256
|
-
image_name,
|
257
|
-
container_name="clarifai-model-container",
|
258
|
-
port=8080,
|
259
|
-
env_vars=None):
|
260
|
-
"""Runs a Docker container from the specified image."""
|
261
|
-
try:
|
262
|
-
logger.info(f"Running Docker container '{container_name}' from image '{image_name}'...")
|
263
|
-
# Base docker run command
|
264
|
-
cmd = ["docker", "run", "--name", container_name, '--rm', "--network", "host"]
|
265
|
-
if self._gpu_is_available():
|
266
|
-
cmd.extend(["--gpus", "all"])
|
267
|
-
# Add volume mappings
|
268
|
-
cmd.extend(["-v", f"{self.model_path}:/home/nonroot/main"])
|
269
|
-
# Add environment variables
|
270
|
-
if env_vars:
|
271
|
-
for key, value in env_vars.items():
|
272
|
-
cmd.extend(["-e", f"{key}={value}"])
|
273
|
-
# Add the image name
|
274
|
-
cmd.append(image_name)
|
275
|
-
# update the CMD to run the server
|
276
|
-
cmd.extend(["--model_path", "/home/nonroot/main", "--grpc", "--port", str(port)])
|
277
|
-
# Run the container
|
278
|
-
process = subprocess.Popen(cmd,)
|
279
|
-
logger.info(
|
280
|
-
f"Docker container '{container_name}' is running successfully! access the model at http://localhost:{port}"
|
281
|
-
)
|
282
|
-
|
283
|
-
# Function to handle Ctrl+C (SIGINT) gracefully
|
284
|
-
def signal_handler(sig, frame):
|
285
|
-
logger.info(f"Stopping Docker container '{container_name}'...")
|
286
|
-
subprocess.run(["docker", "stop", container_name], check=True)
|
287
|
-
process.terminate()
|
288
|
-
logger.info(f"Docker container '{container_name}' stopped successfully!")
|
289
|
-
time.sleep(1)
|
290
|
-
sys.exit(0)
|
291
|
-
|
292
|
-
# Register the signal handler for SIGINT (Ctrl+C)
|
293
|
-
signal.signal(signal.SIGINT, signal_handler)
|
294
|
-
# Wait for the process to finish (keeps the container running until it's stopped)
|
295
|
-
process.wait()
|
296
|
-
except subprocess.CalledProcessError as e:
|
297
|
-
logger.info(f"Error occurred while running the Docker container: {e}")
|
298
|
-
sys.exit(1)
|
299
|
-
except Exception as e:
|
300
|
-
logger.info(f"Error occurred while running the Docker container: {e}")
|
301
|
-
sys.exit(1)
|
302
|
-
|
303
|
-
def test_model_container(self,
|
304
|
-
image_name,
|
305
|
-
container_name="clarifai-model-container",
|
306
|
-
env_vars=None):
|
307
|
-
"""Test the model inside the Docker container."""
|
308
|
-
try:
|
309
|
-
logger.info("Testing the model inside the Docker container...")
|
310
|
-
# Base docker run command
|
311
|
-
cmd = ["docker", "run", "--name", container_name, '--rm', "--network", "host"]
|
312
|
-
if self._gpu_is_available():
|
313
|
-
cmd.extend(["--gpus", "all"])
|
314
|
-
# update the entrypoint for testing the model
|
315
|
-
cmd.extend(["--entrypoint", "python"])
|
316
|
-
# Add volume mappings
|
317
|
-
cmd.extend(["-v", f"{self.model_path}:/home/nonroot/main"])
|
318
|
-
# Add environment variables
|
319
|
-
if env_vars:
|
320
|
-
for key, value in env_vars.items():
|
321
|
-
cmd.extend(["-e", f"{key}={value}"])
|
322
|
-
# Add the image name
|
323
|
-
cmd.append(image_name)
|
324
|
-
# update the CMD to test the model inside the container
|
325
|
-
cmd.extend([
|
326
|
-
"-c",
|
327
|
-
"from clarifai.runners.models.model_run_locally import ModelRunLocally; ModelRunLocally('/home/nonroot/main')._run_test()"
|
328
|
-
])
|
329
|
-
# Run the container
|
330
|
-
subprocess.check_call(cmd)
|
331
|
-
logger.info("Model tested successfully!")
|
332
|
-
except subprocess.CalledProcessError as e:
|
333
|
-
logger.error(f"Error testing the model inside the Docker container: {e}")
|
334
|
-
sys.exit(1)
|
335
|
-
|
336
|
-
def container_exists(self, container_name="clarifai-model-container"):
|
337
|
-
"""Check if the Docker container exists."""
|
338
|
-
try:
|
339
|
-
# Run docker ps -a to list all containers (running and stopped)
|
340
|
-
result = subprocess.run(
|
341
|
-
["docker", "ps", "-a", "--filter", f"name={container_name}", "--format", "{{.Names}}"],
|
342
|
-
check=True,
|
343
|
-
capture_output=True,
|
344
|
-
text=True)
|
345
|
-
# If the container name is returned, it exists
|
346
|
-
if result.stdout.strip() == container_name:
|
347
|
-
logger.info(f"Docker container '{container_name}' exists.")
|
348
|
-
return True
|
349
|
-
else:
|
350
|
-
return False
|
351
|
-
except subprocess.CalledProcessError as e:
|
352
|
-
logger.error(f"Error occurred while checking if container exists: {e}")
|
353
|
-
return False
|
354
|
-
|
355
|
-
def stop_docker_container(self, container_name="clarifai-model-container"):
|
356
|
-
"""Stop the Docker container if it's running."""
|
357
|
-
try:
|
358
|
-
# Check if the container is running
|
359
|
-
result = subprocess.run(
|
360
|
-
["docker", "ps", "--filter", f"name={container_name}", "--format", "{{.Names}}"],
|
361
|
-
check=True,
|
362
|
-
capture_output=True,
|
363
|
-
text=True)
|
364
|
-
if result.stdout.strip() == container_name:
|
365
|
-
logger.info(f"Docker container '{container_name}' is running. Stopping it...")
|
366
|
-
subprocess.run(["docker", "stop", container_name], check=True)
|
367
|
-
logger.info(f"Docker container '{container_name}' stopped successfully!")
|
368
|
-
except subprocess.CalledProcessError as e:
|
369
|
-
logger.error(f"Error occurred while stopping the Docker container: {e}")
|
370
|
-
|
371
|
-
def remove_docker_container(self, container_name="clarifai-model-container"):
|
372
|
-
"""Remove the Docker container."""
|
373
|
-
try:
|
374
|
-
logger.info(f"Removing Docker container '{container_name}'...")
|
375
|
-
subprocess.run(["docker", "rm", container_name], check=True)
|
376
|
-
logger.info(f"Docker container '{container_name}' removed successfully!")
|
377
|
-
except subprocess.CalledProcessError as e:
|
378
|
-
logger.error(f"Error occurred while removing the Docker container: {e}")
|
379
|
-
|
380
|
-
def remove_docker_image(self, image_name):
|
381
|
-
"""Remove the Docker image."""
|
382
|
-
try:
|
383
|
-
logger.info(f"Removing Docker image '{image_name}'...")
|
384
|
-
subprocess.run(["docker", "rmi", image_name], check=True)
|
385
|
-
logger.info(f"Docker image '{image_name}' removed successfully!")
|
386
|
-
except subprocess.CalledProcessError as e:
|
387
|
-
logger.error(f"Error occurred while removing the Docker image: {e}")
|
388
|
-
|
389
|
-
def clean_up(self):
|
390
|
-
"""Clean up the temporary virtual environment."""
|
391
|
-
if os.path.exists(self.temp_dir):
|
392
|
-
logger.info("Cleaning up temporary virtual environment...")
|
393
|
-
shutil.rmtree(self.temp_dir)
|
394
|
-
|
395
|
-
|
396
|
-
def main(model_path,
|
397
|
-
run_model_server=False,
|
398
|
-
inside_container=False,
|
399
|
-
port=8080,
|
400
|
-
keep_env=False,
|
401
|
-
keep_image=False,
|
402
|
-
skip_dockerfile: bool = False):
|
403
|
-
|
404
|
-
manager = ModelRunLocally(model_path)
|
405
|
-
# get whatever stage is in config.yaml to force download now
|
406
|
-
# also always write to where upload/build wants to, not the /tmp folder that runtime stage uses
|
407
|
-
_, _, _, when, _, _ = manager.builder._validate_config_checkpoints()
|
408
|
-
manager.builder.download_checkpoints(
|
409
|
-
stage=when, checkpoint_path_override=manager.builder.checkpoint_path)
|
410
|
-
if inside_container:
|
411
|
-
if not manager.is_docker_installed():
|
412
|
-
sys.exit(1)
|
413
|
-
if not skip_dockerfile:
|
414
|
-
manager.builder.create_dockerfile()
|
415
|
-
image_tag = manager._docker_hash()
|
416
|
-
model_id = manager.config['model']['id'].lower()
|
417
|
-
# must be in lowercase
|
418
|
-
image_name = f"{model_id}:{image_tag}"
|
419
|
-
container_name = model_id
|
420
|
-
if not manager.docker_image_exists(image_name):
|
421
|
-
manager.build_docker_image(image_name=image_name)
|
422
|
-
try:
|
423
|
-
if run_model_server:
|
424
|
-
manager.run_docker_container(
|
425
|
-
image_name=image_name, container_name=container_name, port=port)
|
426
|
-
else:
|
427
|
-
manager.test_model_container(image_name=image_name, container_name=container_name)
|
428
|
-
finally:
|
429
|
-
if manager.container_exists(container_name):
|
430
|
-
manager.stop_docker_container(container_name)
|
431
|
-
manager.remove_docker_container(container_name=container_name)
|
432
|
-
if not keep_image:
|
433
|
-
manager.remove_docker_image(image_name)
|
434
|
-
|
435
|
-
else:
|
436
|
-
try:
|
437
|
-
use_existing_env = manager.create_temp_venv()
|
438
|
-
if not use_existing_env:
|
439
|
-
manager.install_requirements()
|
440
|
-
if run_model_server:
|
441
|
-
manager.run_model_server(port)
|
442
|
-
else:
|
443
|
-
manager.test_model()
|
444
|
-
finally:
|
445
|
-
if not keep_env:
|
446
|
-
manager.clean_up()
|
477
|
+
use_existing_env = manager.create_temp_venv()
|
478
|
+
if not use_existing_env:
|
479
|
+
manager.install_requirements()
|
480
|
+
if run_model_server:
|
481
|
+
manager.run_model_server(port)
|
482
|
+
else:
|
483
|
+
manager.test_model()
|
484
|
+
finally:
|
485
|
+
if not keep_env:
|
486
|
+
manager.clean_up()
|