snowflake-ml-python 1.0.1__py3-none-any.whl → 1.0.3__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.
- snowflake/ml/_internal/env_utils.py +2 -1
- snowflake/ml/_internal/file_utils.py +35 -40
- snowflake/ml/_internal/telemetry.py +5 -8
- snowflake/ml/_internal/utils/identifier.py +74 -7
- snowflake/ml/_internal/utils/uri.py +7 -2
- snowflake/ml/model/_core_requirements.py +1 -1
- snowflake/ml/model/_deploy_client/image_builds/base_image_builder.py +15 -0
- snowflake/ml/model/_deploy_client/image_builds/client_image_builder.py +259 -0
- snowflake/ml/model/_deploy_client/image_builds/docker_context.py +89 -0
- snowflake/ml/model/_deploy_client/image_builds/gunicorn_run.sh +24 -0
- snowflake/ml/model/_deploy_client/image_builds/inference_server/main.py +118 -0
- snowflake/ml/model/_deploy_client/image_builds/templates/dockerfile_template +40 -0
- snowflake/ml/model/_deploy_client/snowservice/deploy.py +199 -0
- snowflake/ml/model/_deploy_client/snowservice/deploy_options.py +88 -0
- snowflake/ml/model/_deploy_client/snowservice/templates/service_spec_template +24 -0
- snowflake/ml/model/_deploy_client/utils/constants.py +47 -0
- snowflake/ml/model/_deploy_client/utils/snowservice_client.py +178 -0
- snowflake/ml/model/_deploy_client/warehouse/deploy.py +25 -28
- snowflake/ml/model/_deploy_client/warehouse/infer_template.py +7 -4
- snowflake/ml/model/_deployer.py +14 -27
- snowflake/ml/model/_env.py +4 -4
- snowflake/ml/model/_handlers/_base.py +3 -1
- snowflake/ml/model/_handlers/custom.py +14 -2
- snowflake/ml/model/_handlers/pytorch.py +186 -0
- snowflake/ml/model/_handlers/sklearn.py +14 -8
- snowflake/ml/model/_handlers/snowmlmodel.py +14 -9
- snowflake/ml/model/_handlers/torchscript.py +180 -0
- snowflake/ml/model/_handlers/xgboost.py +19 -9
- snowflake/ml/model/_model.py +27 -21
- snowflake/ml/model/_model_meta.py +33 -19
- snowflake/ml/model/model_signature.py +446 -66
- snowflake/ml/model/type_hints.py +28 -15
- snowflake/ml/modeling/calibration/calibrated_classifier_cv.py +79 -43
- snowflake/ml/modeling/cluster/affinity_propagation.py +79 -43
- snowflake/ml/modeling/cluster/agglomerative_clustering.py +79 -43
- snowflake/ml/modeling/cluster/birch.py +79 -43
- snowflake/ml/modeling/cluster/bisecting_k_means.py +79 -43
- snowflake/ml/modeling/cluster/dbscan.py +79 -43
- snowflake/ml/modeling/cluster/feature_agglomeration.py +79 -43
- snowflake/ml/modeling/cluster/k_means.py +79 -43
- snowflake/ml/modeling/cluster/mean_shift.py +79 -43
- snowflake/ml/modeling/cluster/mini_batch_k_means.py +79 -43
- snowflake/ml/modeling/cluster/optics.py +79 -43
- snowflake/ml/modeling/cluster/spectral_biclustering.py +79 -43
- snowflake/ml/modeling/cluster/spectral_clustering.py +79 -43
- snowflake/ml/modeling/cluster/spectral_coclustering.py +79 -43
- snowflake/ml/modeling/compose/column_transformer.py +79 -43
- snowflake/ml/modeling/compose/transformed_target_regressor.py +79 -43
- snowflake/ml/modeling/covariance/elliptic_envelope.py +79 -43
- snowflake/ml/modeling/covariance/empirical_covariance.py +79 -43
- snowflake/ml/modeling/covariance/graphical_lasso.py +79 -43
- snowflake/ml/modeling/covariance/graphical_lasso_cv.py +79 -43
- snowflake/ml/modeling/covariance/ledoit_wolf.py +79 -43
- snowflake/ml/modeling/covariance/min_cov_det.py +79 -43
- snowflake/ml/modeling/covariance/oas.py +79 -43
- snowflake/ml/modeling/covariance/shrunk_covariance.py +79 -43
- snowflake/ml/modeling/decomposition/dictionary_learning.py +79 -43
- snowflake/ml/modeling/decomposition/factor_analysis.py +79 -43
- snowflake/ml/modeling/decomposition/fast_ica.py +79 -43
- snowflake/ml/modeling/decomposition/incremental_pca.py +79 -43
- snowflake/ml/modeling/decomposition/kernel_pca.py +79 -43
- snowflake/ml/modeling/decomposition/mini_batch_dictionary_learning.py +79 -43
- snowflake/ml/modeling/decomposition/mini_batch_sparse_pca.py +79 -43
- snowflake/ml/modeling/decomposition/pca.py +79 -43
- snowflake/ml/modeling/decomposition/sparse_pca.py +79 -43
- snowflake/ml/modeling/decomposition/truncated_svd.py +79 -43
- snowflake/ml/modeling/discriminant_analysis/linear_discriminant_analysis.py +79 -43
- snowflake/ml/modeling/discriminant_analysis/quadratic_discriminant_analysis.py +79 -43
- snowflake/ml/modeling/ensemble/ada_boost_classifier.py +79 -43
- snowflake/ml/modeling/ensemble/ada_boost_regressor.py +79 -43
- snowflake/ml/modeling/ensemble/bagging_classifier.py +79 -43
- snowflake/ml/modeling/ensemble/bagging_regressor.py +79 -43
- snowflake/ml/modeling/ensemble/extra_trees_classifier.py +79 -43
- snowflake/ml/modeling/ensemble/extra_trees_regressor.py +79 -43
- snowflake/ml/modeling/ensemble/gradient_boosting_classifier.py +79 -43
- snowflake/ml/modeling/ensemble/gradient_boosting_regressor.py +79 -43
- snowflake/ml/modeling/ensemble/hist_gradient_boosting_classifier.py +79 -43
- snowflake/ml/modeling/ensemble/hist_gradient_boosting_regressor.py +79 -43
- snowflake/ml/modeling/ensemble/isolation_forest.py +79 -43
- snowflake/ml/modeling/ensemble/random_forest_classifier.py +79 -43
- snowflake/ml/modeling/ensemble/random_forest_regressor.py +79 -43
- snowflake/ml/modeling/ensemble/stacking_regressor.py +79 -43
- snowflake/ml/modeling/ensemble/voting_classifier.py +79 -43
- snowflake/ml/modeling/ensemble/voting_regressor.py +79 -43
- snowflake/ml/modeling/feature_selection/generic_univariate_select.py +79 -43
- snowflake/ml/modeling/feature_selection/select_fdr.py +79 -43
- snowflake/ml/modeling/feature_selection/select_fpr.py +79 -43
- snowflake/ml/modeling/feature_selection/select_fwe.py +79 -43
- snowflake/ml/modeling/feature_selection/select_k_best.py +79 -43
- snowflake/ml/modeling/feature_selection/select_percentile.py +79 -43
- snowflake/ml/modeling/feature_selection/sequential_feature_selector.py +79 -43
- snowflake/ml/modeling/feature_selection/variance_threshold.py +79 -43
- snowflake/ml/modeling/gaussian_process/gaussian_process_classifier.py +79 -43
- snowflake/ml/modeling/gaussian_process/gaussian_process_regressor.py +79 -43
- snowflake/ml/modeling/impute/iterative_imputer.py +79 -43
- snowflake/ml/modeling/impute/knn_imputer.py +79 -43
- snowflake/ml/modeling/impute/missing_indicator.py +79 -43
- snowflake/ml/modeling/kernel_approximation/additive_chi2_sampler.py +79 -43
- snowflake/ml/modeling/kernel_approximation/nystroem.py +79 -43
- snowflake/ml/modeling/kernel_approximation/polynomial_count_sketch.py +79 -43
- snowflake/ml/modeling/kernel_approximation/rbf_sampler.py +79 -43
- snowflake/ml/modeling/kernel_approximation/skewed_chi2_sampler.py +79 -43
- snowflake/ml/modeling/kernel_ridge/kernel_ridge.py +79 -43
- snowflake/ml/modeling/lightgbm/lgbm_classifier.py +79 -43
- snowflake/ml/modeling/lightgbm/lgbm_regressor.py +79 -43
- snowflake/ml/modeling/linear_model/ard_regression.py +79 -43
- snowflake/ml/modeling/linear_model/bayesian_ridge.py +79 -43
- snowflake/ml/modeling/linear_model/elastic_net.py +79 -43
- snowflake/ml/modeling/linear_model/elastic_net_cv.py +79 -43
- snowflake/ml/modeling/linear_model/gamma_regressor.py +79 -43
- snowflake/ml/modeling/linear_model/huber_regressor.py +79 -43
- snowflake/ml/modeling/linear_model/lars.py +79 -43
- snowflake/ml/modeling/linear_model/lars_cv.py +79 -43
- snowflake/ml/modeling/linear_model/lasso.py +79 -43
- snowflake/ml/modeling/linear_model/lasso_cv.py +79 -43
- snowflake/ml/modeling/linear_model/lasso_lars.py +79 -43
- snowflake/ml/modeling/linear_model/lasso_lars_cv.py +79 -43
- snowflake/ml/modeling/linear_model/lasso_lars_ic.py +79 -43
- snowflake/ml/modeling/linear_model/linear_regression.py +79 -43
- snowflake/ml/modeling/linear_model/logistic_regression.py +79 -43
- snowflake/ml/modeling/linear_model/logistic_regression_cv.py +79 -43
- snowflake/ml/modeling/linear_model/multi_task_elastic_net.py +79 -43
- snowflake/ml/modeling/linear_model/multi_task_elastic_net_cv.py +79 -43
- snowflake/ml/modeling/linear_model/multi_task_lasso.py +79 -43
- snowflake/ml/modeling/linear_model/multi_task_lasso_cv.py +79 -43
- snowflake/ml/modeling/linear_model/orthogonal_matching_pursuit.py +79 -43
- snowflake/ml/modeling/linear_model/passive_aggressive_classifier.py +79 -43
- snowflake/ml/modeling/linear_model/passive_aggressive_regressor.py +79 -43
- snowflake/ml/modeling/linear_model/perceptron.py +79 -43
- snowflake/ml/modeling/linear_model/poisson_regressor.py +79 -43
- snowflake/ml/modeling/linear_model/ransac_regressor.py +79 -43
- snowflake/ml/modeling/linear_model/ridge.py +79 -43
- snowflake/ml/modeling/linear_model/ridge_classifier.py +79 -43
- snowflake/ml/modeling/linear_model/ridge_classifier_cv.py +79 -43
- snowflake/ml/modeling/linear_model/ridge_cv.py +79 -43
- snowflake/ml/modeling/linear_model/sgd_classifier.py +79 -43
- snowflake/ml/modeling/linear_model/sgd_one_class_svm.py +79 -43
- snowflake/ml/modeling/linear_model/sgd_regressor.py +79 -43
- snowflake/ml/modeling/linear_model/theil_sen_regressor.py +79 -43
- snowflake/ml/modeling/linear_model/tweedie_regressor.py +79 -43
- snowflake/ml/modeling/manifold/isomap.py +79 -43
- snowflake/ml/modeling/manifold/mds.py +79 -43
- snowflake/ml/modeling/manifold/spectral_embedding.py +79 -43
- snowflake/ml/modeling/manifold/tsne.py +79 -43
- snowflake/ml/modeling/metrics/classification.py +6 -1
- snowflake/ml/modeling/metrics/regression.py +517 -9
- snowflake/ml/modeling/mixture/bayesian_gaussian_mixture.py +79 -43
- snowflake/ml/modeling/mixture/gaussian_mixture.py +79 -43
- snowflake/ml/modeling/model_selection/grid_search_cv.py +79 -43
- snowflake/ml/modeling/model_selection/randomized_search_cv.py +79 -43
- snowflake/ml/modeling/multiclass/one_vs_one_classifier.py +79 -43
- snowflake/ml/modeling/multiclass/one_vs_rest_classifier.py +79 -43
- snowflake/ml/modeling/multiclass/output_code_classifier.py +79 -43
- snowflake/ml/modeling/naive_bayes/bernoulli_nb.py +79 -43
- snowflake/ml/modeling/naive_bayes/categorical_nb.py +79 -43
- snowflake/ml/modeling/naive_bayes/complement_nb.py +79 -43
- snowflake/ml/modeling/naive_bayes/gaussian_nb.py +79 -43
- snowflake/ml/modeling/naive_bayes/multinomial_nb.py +79 -43
- snowflake/ml/modeling/neighbors/k_neighbors_classifier.py +79 -43
- snowflake/ml/modeling/neighbors/k_neighbors_regressor.py +79 -43
- snowflake/ml/modeling/neighbors/kernel_density.py +79 -43
- snowflake/ml/modeling/neighbors/local_outlier_factor.py +79 -43
- snowflake/ml/modeling/neighbors/nearest_centroid.py +79 -43
- snowflake/ml/modeling/neighbors/nearest_neighbors.py +79 -43
- snowflake/ml/modeling/neighbors/neighborhood_components_analysis.py +79 -43
- snowflake/ml/modeling/neighbors/radius_neighbors_classifier.py +79 -43
- snowflake/ml/modeling/neighbors/radius_neighbors_regressor.py +79 -43
- snowflake/ml/modeling/neural_network/bernoulli_rbm.py +79 -43
- snowflake/ml/modeling/neural_network/mlp_classifier.py +79 -43
- snowflake/ml/modeling/neural_network/mlp_regressor.py +79 -43
- snowflake/ml/modeling/pipeline/pipeline.py +24 -0
- snowflake/ml/modeling/preprocessing/one_hot_encoder.py +18 -19
- snowflake/ml/modeling/preprocessing/ordinal_encoder.py +2 -0
- snowflake/ml/modeling/preprocessing/polynomial_features.py +79 -43
- snowflake/ml/modeling/semi_supervised/label_propagation.py +79 -43
- snowflake/ml/modeling/semi_supervised/label_spreading.py +79 -43
- snowflake/ml/modeling/svm/linear_svc.py +79 -43
- snowflake/ml/modeling/svm/linear_svr.py +79 -43
- snowflake/ml/modeling/svm/nu_svc.py +79 -43
- snowflake/ml/modeling/svm/nu_svr.py +79 -43
- snowflake/ml/modeling/svm/svc.py +79 -43
- snowflake/ml/modeling/svm/svr.py +79 -43
- snowflake/ml/modeling/tree/decision_tree_classifier.py +79 -43
- snowflake/ml/modeling/tree/decision_tree_regressor.py +79 -43
- snowflake/ml/modeling/tree/extra_tree_classifier.py +79 -43
- snowflake/ml/modeling/tree/extra_tree_regressor.py +79 -43
- snowflake/ml/modeling/xgboost/xgb_classifier.py +79 -43
- snowflake/ml/modeling/xgboost/xgb_regressor.py +79 -43
- snowflake/ml/modeling/xgboost/xgbrf_classifier.py +79 -43
- snowflake/ml/modeling/xgboost/xgbrf_regressor.py +79 -43
- snowflake/ml/registry/model_registry.py +123 -121
- snowflake/ml/version.py +1 -1
- {snowflake_ml_python-1.0.1.dist-info → snowflake_ml_python-1.0.3.dist-info}/METADATA +50 -8
- snowflake_ml_python-1.0.3.dist-info/RECORD +259 -0
- snowflake_ml_python-1.0.1.dist-info/RECORD +0 -246
- {snowflake_ml_python-1.0.1.dist-info → snowflake_ml_python-1.0.3.dist-info}/WHEEL +0 -0
@@ -0,0 +1,89 @@
|
|
1
|
+
import importlib
|
2
|
+
import os
|
3
|
+
import shutil
|
4
|
+
import string
|
5
|
+
from abc import ABC
|
6
|
+
|
7
|
+
from snowflake.ml.model._deploy_client.utils import constants
|
8
|
+
|
9
|
+
|
10
|
+
class DockerContext(ABC):
|
11
|
+
"""
|
12
|
+
Constructs the Docker context directory required for image building.
|
13
|
+
"""
|
14
|
+
|
15
|
+
def __init__(self, context_dir: str, model_dir: str, *, use_gpu: bool = False) -> None:
|
16
|
+
"""Initialization
|
17
|
+
|
18
|
+
Args:
|
19
|
+
context_dir: Path to context directory.
|
20
|
+
model_dir: Path to local model directory.
|
21
|
+
use_gpu: Boolean flag for generating the CPU or GPU base image.
|
22
|
+
"""
|
23
|
+
self.context_dir = context_dir
|
24
|
+
self.model_dir = model_dir
|
25
|
+
# TODO(shchen): SNOW-825995, Define dockerfile template used for model deployment. use_gpu will be used.
|
26
|
+
self.use_gpu = use_gpu
|
27
|
+
|
28
|
+
def build(self) -> None:
|
29
|
+
"""
|
30
|
+
Generates and/or moves resources into the Docker context directory.Rename the random model directory name to
|
31
|
+
constant "model_dir" instead for better readability.
|
32
|
+
"""
|
33
|
+
self._generate_inference_code()
|
34
|
+
self._copy_entrypoint_script_to_docker_context()
|
35
|
+
self._copy_snowml_source_code_to_docker_context()
|
36
|
+
self._generate_docker_file()
|
37
|
+
|
38
|
+
def _copy_snowml_source_code_to_docker_context(self) -> None:
|
39
|
+
"""Copy the entire snowflake/ml source code to docker context. This will be particularly useful for CI tests
|
40
|
+
against latest changes.
|
41
|
+
|
42
|
+
Note that we exclude the experimental directory mainly for development scenario; as experimental directory won't
|
43
|
+
be included in the release.
|
44
|
+
"""
|
45
|
+
snow_ml_source_dir = list(importlib.import_module("snowflake.ml").__path__)[0]
|
46
|
+
shutil.copytree(
|
47
|
+
snow_ml_source_dir,
|
48
|
+
os.path.join(self.context_dir, "snowflake", "ml"),
|
49
|
+
ignore=shutil.ignore_patterns("*.pyc", "experimental"),
|
50
|
+
)
|
51
|
+
|
52
|
+
def _copy_entrypoint_script_to_docker_context(self) -> None:
|
53
|
+
"""Copy gunicorn_run.sh entrypoint to docker context directory."""
|
54
|
+
path = os.path.join(os.path.dirname(__file__), constants.ENTRYPOINT_SCRIPT)
|
55
|
+
assert os.path.exists(path), f"Run script file missing at path: {path}"
|
56
|
+
shutil.copy(path, os.path.join(self.context_dir, constants.ENTRYPOINT_SCRIPT))
|
57
|
+
|
58
|
+
def _generate_docker_file(self) -> None:
|
59
|
+
"""
|
60
|
+
Generates dockerfile based on dockerfile template.
|
61
|
+
"""
|
62
|
+
docker_file_path = os.path.join(self.context_dir, "Dockerfile")
|
63
|
+
docker_file_template = os.path.join(os.path.dirname(__file__), "templates/dockerfile_template")
|
64
|
+
|
65
|
+
with open(docker_file_path, "w", encoding="utf-8") as dockerfile, open(
|
66
|
+
docker_file_template, encoding="utf-8"
|
67
|
+
) as template:
|
68
|
+
dockerfile_content = string.Template(template.read()).safe_substitute(
|
69
|
+
{
|
70
|
+
# TODO(shchen): SNOW-835411, Support overwriting base image
|
71
|
+
"base_image": "mambaorg/micromamba:focal-cuda-11.7.1"
|
72
|
+
if self.use_gpu
|
73
|
+
else "mambaorg/micromamba:1.4.3",
|
74
|
+
"model_dir": constants.MODEL_DIR,
|
75
|
+
"inference_server_dir": constants.INFERENCE_SERVER_DIR,
|
76
|
+
"entrypoint_script": constants.ENTRYPOINT_SCRIPT,
|
77
|
+
}
|
78
|
+
)
|
79
|
+
dockerfile.write(dockerfile_content)
|
80
|
+
|
81
|
+
def _generate_inference_code(self) -> None:
|
82
|
+
"""
|
83
|
+
Generates inference code based on the app template and creates a folder named 'server' to house the inference
|
84
|
+
server code.
|
85
|
+
"""
|
86
|
+
inference_server_folder_path = os.path.join(os.path.dirname(__file__), constants.INFERENCE_SERVER_DIR)
|
87
|
+
destination_folder_path = os.path.join(self.context_dir, constants.INFERENCE_SERVER_DIR)
|
88
|
+
ignore_patterns = shutil.ignore_patterns("BUILD.bazel", "*test.py", "*.\\.*", "__pycache__")
|
89
|
+
shutil.copytree(inference_server_folder_path, destination_folder_path, ignore=ignore_patterns)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
set -eu
|
3
|
+
|
4
|
+
OS=$(uname)
|
5
|
+
|
6
|
+
if [ "${OS}" = "Linux" ]; then
|
7
|
+
NUM_CORES=$(nproc)
|
8
|
+
elif [ "${OS}" = "Darwin" ]; then
|
9
|
+
# macOS
|
10
|
+
NUM_CORES=$(sysctl -n hw.ncpu)
|
11
|
+
elif [ "${OS}" = "Windows" ]; then
|
12
|
+
NUM_CORES=$(wmic cpu get NumberOfCores | grep -Eo '[0-9]+')
|
13
|
+
else
|
14
|
+
echo "Unsupported operating system: ${OS}"
|
15
|
+
exit 1
|
16
|
+
fi
|
17
|
+
|
18
|
+
# Based on the Gunicorn documentation, set the number of workers to number_of_cores * 2 + 1. This assumption is
|
19
|
+
# based on an ideal scenario where one core is handling two processes simultaneously, while one process is dedicated to
|
20
|
+
# IO operations and the other process is performing compute tasks.
|
21
|
+
NUM_WORKERS=$((NUM_CORES * 2 + 1))
|
22
|
+
echo "Number of CPU cores: $NUM_CORES"
|
23
|
+
echo "Setting number of workers to $NUM_WORKERS"
|
24
|
+
exec /opt/conda/bin/gunicorn --preload -w "$NUM_WORKERS" -k uvicorn.workers.UvicornWorker -b 0.0.0.0:5000 inference_server.main:app
|
@@ -0,0 +1,118 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
import tempfile
|
4
|
+
import zipfile
|
5
|
+
|
6
|
+
import pandas as pd
|
7
|
+
from starlette import applications, requests, responses, routing
|
8
|
+
|
9
|
+
logger = logging.getLogger(__name__)
|
10
|
+
loaded_model = None
|
11
|
+
|
12
|
+
|
13
|
+
def _run_setup() -> None:
|
14
|
+
"""Set up logging and load model into memory."""
|
15
|
+
# Align the application logger's handler with Gunicorn's to capture logs from all processes.
|
16
|
+
gunicorn_logger = logging.getLogger("gunicorn.error")
|
17
|
+
logger.handlers = gunicorn_logger.handlers
|
18
|
+
logger.setLevel(gunicorn_logger.level)
|
19
|
+
|
20
|
+
from snowflake.ml.model import _model as model_api
|
21
|
+
|
22
|
+
global loaded_model
|
23
|
+
|
24
|
+
MODEL_ZIP_STAGE_PATH = os.getenv("MODEL_ZIP_STAGE_PATH")
|
25
|
+
assert MODEL_ZIP_STAGE_PATH, "Missing environment variable MODEL_ZIP_STAGE_PATH"
|
26
|
+
root_path = os.path.abspath(os.sep)
|
27
|
+
model_zip_stage_path = os.path.join(root_path, MODEL_ZIP_STAGE_PATH)
|
28
|
+
|
29
|
+
with tempfile.TemporaryDirectory() as tmp_dir:
|
30
|
+
if zipfile.is_zipfile(model_zip_stage_path):
|
31
|
+
extracted_dir = os.path.join(tmp_dir, "extracted_model_dir")
|
32
|
+
logger.info(f"Extracting model zip from {model_zip_stage_path} to {extracted_dir}")
|
33
|
+
with zipfile.ZipFile(model_zip_stage_path, "r") as model_zip:
|
34
|
+
if len(model_zip.namelist()) > 1:
|
35
|
+
model_zip.extractall(extracted_dir)
|
36
|
+
else:
|
37
|
+
raise RuntimeError(f"No model zip found at stage path: {model_zip_stage_path}")
|
38
|
+
logger.info(f"Loading model from {extracted_dir} into memory")
|
39
|
+
loaded_model, _ = model_api._load_model_for_deploy(model_dir_path=extracted_dir)
|
40
|
+
logger.info("Successfully loaded model into memory")
|
41
|
+
|
42
|
+
|
43
|
+
async def ready(request: requests.Request) -> responses.JSONResponse:
|
44
|
+
"""Endpoint to check if the application is ready."""
|
45
|
+
return responses.JSONResponse({"status": "ready"})
|
46
|
+
|
47
|
+
|
48
|
+
async def predict(request: requests.Request) -> responses.JSONResponse:
|
49
|
+
"""Endpoint to make predictions based on input data.
|
50
|
+
|
51
|
+
Args:
|
52
|
+
request: The input data is expected to be in the following JSON format:
|
53
|
+
{
|
54
|
+
"data": [
|
55
|
+
[0, 5.1, 3.5, 4.2, 1.3],
|
56
|
+
[1, 4.7, 3.2, 4.1, 4.2]
|
57
|
+
}
|
58
|
+
Each row is represented as a list, where the first element denotes the index of the row.
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
Two possible responses:
|
62
|
+
For success, return a JSON response {"data": [[0, 1], [1, 2]]}, where the first element of each resulting list
|
63
|
+
denotes the index of the row, and the rest of the elements represent the prediction results for that row.
|
64
|
+
For an error, return {"error": error_message, "status_code": http_response_status_code}.
|
65
|
+
"""
|
66
|
+
try:
|
67
|
+
input = await request.json()
|
68
|
+
assert "data" in input, "missing data field in the request input"
|
69
|
+
# The expression x[1:] is used to exclude the index of the data row.
|
70
|
+
input_data = [x[1:] for x in input.get("data")]
|
71
|
+
x = pd.DataFrame(input_data)
|
72
|
+
assert len(input_data) != 0 and not all(not row for row in input_data), "empty data"
|
73
|
+
except Exception as e:
|
74
|
+
error_message = f"Input data malformed: {str(e)}"
|
75
|
+
return responses.JSONResponse({"error": error_message}, status_code=400)
|
76
|
+
|
77
|
+
assert loaded_model
|
78
|
+
|
79
|
+
try:
|
80
|
+
# TODO(shchen): SNOW-835369, Support target method in inference server (Multi-task model).
|
81
|
+
# Mypy ignore will be fixed along with the above ticket.
|
82
|
+
predictions = loaded_model.predict(x) # type: ignore[attr-defined]
|
83
|
+
result = predictions.to_records(index=True).tolist()
|
84
|
+
response = {"data": result}
|
85
|
+
return responses.JSONResponse(response)
|
86
|
+
except Exception as e:
|
87
|
+
error_message = f"Prediction failed: {str(e)}"
|
88
|
+
return responses.JSONResponse({"error": error_message}, status_code=400)
|
89
|
+
|
90
|
+
|
91
|
+
def _in_test_mode() -> bool:
|
92
|
+
"""Check if the code is running in test mode.
|
93
|
+
|
94
|
+
Specifically, it checks for the presence of
|
95
|
+
- "PYTEST_CURRENT_TEST" environment variable, which is automatically set by Pytest when running tests, and
|
96
|
+
- "TEST_WORKSPACE" environment variable, which is set by Bazel test, and
|
97
|
+
- "TEST_SRCDIR" environment variable, which is set by the Absl test.
|
98
|
+
|
99
|
+
Returns:
|
100
|
+
True if in test mode; otherwise, returns False
|
101
|
+
"""
|
102
|
+
is_running_under_py_test = "PYTEST_CURRENT_TEST" in os.environ
|
103
|
+
is_running_under_bazel_test = "TEST_WORKSPACE" in os.environ
|
104
|
+
is_running_under_absl_test = "TEST_SRCDIR" in os.environ
|
105
|
+
return is_running_under_py_test or is_running_under_bazel_test or is_running_under_absl_test
|
106
|
+
|
107
|
+
|
108
|
+
def run_app() -> applications.Starlette:
|
109
|
+
if not _in_test_mode():
|
110
|
+
_run_setup()
|
111
|
+
routes = [
|
112
|
+
routing.Route("/health", endpoint=ready, methods=["GET"]),
|
113
|
+
routing.Route("/predict", endpoint=predict, methods=["POST"]),
|
114
|
+
]
|
115
|
+
return applications.Starlette(routes=routes)
|
116
|
+
|
117
|
+
|
118
|
+
app = run_app()
|
@@ -0,0 +1,40 @@
|
|
1
|
+
FROM $base_image as build
|
2
|
+
|
3
|
+
COPY $model_dir/env/conda.yaml conda.yaml
|
4
|
+
COPY $model_dir/env/requirements.txt requirements.txt
|
5
|
+
|
6
|
+
# Set MAMBA_DOCKERFILE_ACTIVATE=1 to activate the conda environment during build time.
|
7
|
+
ARG MAMBA_DOCKERFILE_ACTIVATE=1
|
8
|
+
|
9
|
+
# The micromamba image comes with an empty environment named base.
|
10
|
+
RUN --mount=type=cache,target=/opt/conda/pkgs micromamba install -y -n base -f conda.yaml && \
|
11
|
+
python -m pip install "uvicorn[standard]" gunicorn starlette && \
|
12
|
+
python -m pip install -r requirements.txt
|
13
|
+
|
14
|
+
FROM debian:buster-slim AS runtime
|
15
|
+
|
16
|
+
ENV USER nonrootuser
|
17
|
+
ENV UID 1000
|
18
|
+
ENV HOME /home/$USER
|
19
|
+
RUN adduser --disabled-password \
|
20
|
+
--gecos "A non-root user for running inference server" \
|
21
|
+
--uid $UID \
|
22
|
+
--home $HOME \
|
23
|
+
$USER
|
24
|
+
|
25
|
+
COPY $inference_server_dir ./$inference_server_dir
|
26
|
+
COPY $entrypoint_script ./$entrypoint_script
|
27
|
+
RUN chmod +x /$entrypoint_script
|
28
|
+
# Copy Snowflake/ml source code
|
29
|
+
# TODO: not needed as source code is either in model, or pulled from conda
|
30
|
+
COPY snowflake ./snowflake
|
31
|
+
|
32
|
+
# The mamba root prefix by default is set to /opt/conda, in which the base conda environment is built at.
|
33
|
+
COPY --from=build /opt/conda /opt/conda
|
34
|
+
|
35
|
+
# Expose the port on which the Starlette app will run.
|
36
|
+
EXPOSE 5000
|
37
|
+
|
38
|
+
USER nonrootuser
|
39
|
+
|
40
|
+
CMD ["/$entrypoint_script"]
|
@@ -0,0 +1,199 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
import posixpath
|
4
|
+
import string
|
5
|
+
import tempfile
|
6
|
+
from abc import ABC
|
7
|
+
from typing import Any, Dict, cast
|
8
|
+
|
9
|
+
from typing_extensions import Unpack
|
10
|
+
|
11
|
+
from snowflake.ml.model._deploy_client.image_builds import (
|
12
|
+
base_image_builder,
|
13
|
+
client_image_builder,
|
14
|
+
)
|
15
|
+
from snowflake.ml.model._deploy_client.snowservice import deploy_options
|
16
|
+
from snowflake.ml.model._deploy_client.utils import constants, snowservice_client
|
17
|
+
from snowflake.snowpark import Session
|
18
|
+
|
19
|
+
|
20
|
+
def _deploy(
|
21
|
+
session: Session,
|
22
|
+
*,
|
23
|
+
model_id: str,
|
24
|
+
service_func_name: str,
|
25
|
+
model_zip_stage_path: str,
|
26
|
+
**kwargs: Unpack[deploy_options.SnowServiceDeployOptionsTypedHint],
|
27
|
+
) -> None:
|
28
|
+
"""Entrypoint for model deployment to SnowService. This function will trigger a docker image build followed by
|
29
|
+
workflow deployment to SnowService.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
session: Snowpark session
|
33
|
+
model_id: Unique hex string of length 32, provided by model registry.
|
34
|
+
service_func_name: The service function name in SnowService associated with the created service.
|
35
|
+
model_zip_stage_path: Path to model zip file in stage. Note that this path has a "@" prefix.
|
36
|
+
**kwargs: various SnowService deployment options.
|
37
|
+
|
38
|
+
Raises:
|
39
|
+
ValueError: Raised when model_id is empty.
|
40
|
+
ValueError: Raised when service_func_name is empty.
|
41
|
+
ValueError: Raised when model_stage_file_path is empty.
|
42
|
+
"""
|
43
|
+
snowpark_logger = logging.getLogger("snowflake.snowpark")
|
44
|
+
snowflake_connector_logger = logging.getLogger("snowflake.connector")
|
45
|
+
snowpark_log_level = snowpark_logger.level
|
46
|
+
snowflake_connector_log_level = snowflake_connector_logger.level
|
47
|
+
try:
|
48
|
+
# Setting appropriate log level to prevent console from being polluted by vast amount of snowpark and snowflake
|
49
|
+
# connector logging.
|
50
|
+
snowpark_logger.setLevel(logging.WARNING)
|
51
|
+
snowflake_connector_logger.setLevel(logging.WARNING)
|
52
|
+
if not model_id:
|
53
|
+
raise ValueError('Must provide a non-empty string for "model_id" when deploying to SnowService')
|
54
|
+
if not service_func_name:
|
55
|
+
raise ValueError('Must provide a non-empty string for "service_func_name" when deploying to SnowService')
|
56
|
+
if not model_zip_stage_path:
|
57
|
+
raise ValueError(
|
58
|
+
'Must provide a non-empty string for "model_stage_file_path" when deploying to SnowService'
|
59
|
+
)
|
60
|
+
assert model_zip_stage_path.startswith("@"), f"stage path should start with @, actual: {model_zip_stage_path}"
|
61
|
+
options = deploy_options.SnowServiceDeployOptions.from_dict(cast(Dict[str, Any], kwargs))
|
62
|
+
image_builder = client_image_builder.ClientImageBuilder(
|
63
|
+
id=model_id, image_repo=options.image_repo, model_zip_stage_path=model_zip_stage_path, session=session
|
64
|
+
)
|
65
|
+
ss_deployment = SnowServiceDeployment(
|
66
|
+
session=session,
|
67
|
+
model_id=model_id,
|
68
|
+
service_func_name=service_func_name,
|
69
|
+
model_zip_stage_path=model_zip_stage_path,
|
70
|
+
image_builder=image_builder,
|
71
|
+
options=options,
|
72
|
+
)
|
73
|
+
ss_deployment.deploy()
|
74
|
+
finally:
|
75
|
+
# Preserve the original logging level.
|
76
|
+
snowpark_logger.setLevel(snowpark_log_level)
|
77
|
+
snowflake_connector_logger.setLevel(snowflake_connector_log_level)
|
78
|
+
|
79
|
+
|
80
|
+
class SnowServiceDeployment(ABC):
|
81
|
+
"""
|
82
|
+
Class implementation that encapsulates image build and workflow deployment to SnowService
|
83
|
+
|
84
|
+
#TODO[shchen], SNOW-830093 GPU support on model deployment to SnowService
|
85
|
+
"""
|
86
|
+
|
87
|
+
def __init__(
|
88
|
+
self,
|
89
|
+
session: Session,
|
90
|
+
model_id: str,
|
91
|
+
service_func_name: str,
|
92
|
+
model_zip_stage_path: str,
|
93
|
+
image_builder: base_image_builder.ImageBuilder,
|
94
|
+
options: deploy_options.SnowServiceDeployOptions,
|
95
|
+
) -> None:
|
96
|
+
"""Initialization
|
97
|
+
|
98
|
+
Args:
|
99
|
+
session: Snowpark session
|
100
|
+
model_id: Unique hex string of length 32, provided by model registry; if not provided, auto-generate one for
|
101
|
+
resource naming.The model_id serves as an idempotent key throughout the deployment workflow.
|
102
|
+
service_func_name: The service function name in SnowService associated with the created service.
|
103
|
+
model_zip_stage_path: Path to model zip file in stage.
|
104
|
+
image_builder: InferenceImageBuilder instance that handles image build and upload to image registry.
|
105
|
+
options: A SnowServiceDeployOptions object containing deployment options.
|
106
|
+
"""
|
107
|
+
|
108
|
+
self.session = session
|
109
|
+
self.id = model_id
|
110
|
+
self.service_func_name = service_func_name
|
111
|
+
self.model_zip_stage_path = model_zip_stage_path
|
112
|
+
self.image_builder = image_builder
|
113
|
+
self.options = options
|
114
|
+
self._service_name = f"service_{model_id}"
|
115
|
+
# Spec file and future deployment related artifacts will be stored under {stage}/models/{model_id}
|
116
|
+
self._model_artifact_stage_location = posixpath.join(options.stage, "models", self.id)
|
117
|
+
|
118
|
+
def deploy(self) -> None:
|
119
|
+
"""
|
120
|
+
This function triggers image build followed by workflow deployment to SnowService.
|
121
|
+
"""
|
122
|
+
if self.options.prebuilt_snowflake_image:
|
123
|
+
image = self.options.prebuilt_snowflake_image
|
124
|
+
logging.info(f"Skipped image build. Use Snowflake prebuilt image: {self.options.prebuilt_snowflake_image}")
|
125
|
+
else:
|
126
|
+
image = self._build_and_upload_image()
|
127
|
+
self._deploy_workflow(image)
|
128
|
+
|
129
|
+
def _build_and_upload_image(self) -> str:
|
130
|
+
"""This function handles image build and upload to image registry.
|
131
|
+
|
132
|
+
Returns:
|
133
|
+
Path to the image in the remote image repository.
|
134
|
+
"""
|
135
|
+
return self.image_builder.build_and_upload_image()
|
136
|
+
|
137
|
+
def _prepare_and_upload_artifacts_to_stage(self, image: str) -> None:
|
138
|
+
"""Constructs and upload service spec to stage.
|
139
|
+
|
140
|
+
Args:
|
141
|
+
image: Name of the image to create SnowService container from.
|
142
|
+
"""
|
143
|
+
|
144
|
+
with tempfile.TemporaryDirectory() as tempdir:
|
145
|
+
spec_template_path = os.path.join(os.path.dirname(__file__), "templates/service_spec_template")
|
146
|
+
spec_file_path = os.path.join(tempdir, f"{constants.SERVICE_SPEC}.yaml")
|
147
|
+
|
148
|
+
with open(spec_template_path, encoding="utf-8") as template, open(
|
149
|
+
spec_file_path, "w", encoding="utf-8"
|
150
|
+
) as spec_file:
|
151
|
+
content = string.Template(template.read()).substitute(
|
152
|
+
{
|
153
|
+
"image": image,
|
154
|
+
"predict_endpoint_name": constants.PREDICT,
|
155
|
+
"stage": self.options.stage,
|
156
|
+
"model_zip_stage_path": self.model_zip_stage_path[1:], # Remove the @ prefix
|
157
|
+
"inference_server_container_name": constants.INFERENCE_SERVER_CONTAINER,
|
158
|
+
}
|
159
|
+
)
|
160
|
+
spec_file.write(content)
|
161
|
+
logging.info(f"Create service spec: \n {content}")
|
162
|
+
|
163
|
+
self.session.file.put(
|
164
|
+
local_file_name=spec_file_path,
|
165
|
+
stage_location=self._model_artifact_stage_location,
|
166
|
+
auto_compress=False,
|
167
|
+
overwrite=True,
|
168
|
+
)
|
169
|
+
logging.info(
|
170
|
+
f"Uploaded spec file {os.path.basename(spec_file_path)} " f"to {self._model_artifact_stage_location}"
|
171
|
+
)
|
172
|
+
|
173
|
+
def _deploy_workflow(self, image: str) -> None:
|
174
|
+
"""This function handles workflow deployment to SnowService with the given image.
|
175
|
+
|
176
|
+
Args:
|
177
|
+
image: Name of the image to create SnowService container from.
|
178
|
+
"""
|
179
|
+
|
180
|
+
self._prepare_and_upload_artifacts_to_stage(image)
|
181
|
+
client = snowservice_client.SnowServiceClient(self.session)
|
182
|
+
spec_stage_location = posixpath.join(
|
183
|
+
self._model_artifact_stage_location.rstrip("/"), f"{constants.SERVICE_SPEC}.yaml"
|
184
|
+
)
|
185
|
+
client.create_or_replace_service(
|
186
|
+
service_name=self._service_name,
|
187
|
+
compute_pool=self.options.compute_pool,
|
188
|
+
spec_stage_location=spec_stage_location,
|
189
|
+
min_instances=self.options.min_instances,
|
190
|
+
max_instances=self.options.max_instances,
|
191
|
+
)
|
192
|
+
client.block_until_resource_is_ready(
|
193
|
+
resource_name=self._service_name, resource_type=constants.ResourceType.SERVICE
|
194
|
+
)
|
195
|
+
client.create_or_replace_service_function(
|
196
|
+
service_func_name=self.service_func_name,
|
197
|
+
service_name=self._service_name,
|
198
|
+
endpoint_name=constants.PREDICT,
|
199
|
+
)
|
@@ -0,0 +1,88 @@
|
|
1
|
+
from typing import Any, Dict, Optional, TypedDict
|
2
|
+
|
3
|
+
from typing_extensions import NotRequired
|
4
|
+
|
5
|
+
from snowflake.ml.model._deploy_client.utils import constants
|
6
|
+
|
7
|
+
|
8
|
+
class SnowServiceDeployOptionsTypedHint(TypedDict):
|
9
|
+
"""Deployment options for deploying to SnowService.
|
10
|
+
|
11
|
+
stage: the name of the stage for uploading artifacts.
|
12
|
+
compute_pool: SnowService compute pool name.
|
13
|
+
image_repo: SnowService image repo path. e.g. "<image_registry>/<db>/<schema>/<repo>"
|
14
|
+
min_instances: Minimum number of service replicas.
|
15
|
+
max_instances: Maximum number of service replicas.
|
16
|
+
endpoint: The specific name of the endpoint that the service function will communicate with. Default to
|
17
|
+
"predict". This option is useful when service has multiple endpoints.
|
18
|
+
overridden_base_image: When provided, it will override the base image.
|
19
|
+
"""
|
20
|
+
|
21
|
+
stage: str
|
22
|
+
compute_pool: str
|
23
|
+
image_repo: str
|
24
|
+
min_instances: NotRequired[int]
|
25
|
+
max_instances: NotRequired[int]
|
26
|
+
endpoint: NotRequired[str]
|
27
|
+
overridden_base_image: NotRequired[str]
|
28
|
+
|
29
|
+
|
30
|
+
class SnowServiceDeployOptions:
|
31
|
+
def __init__(
|
32
|
+
self,
|
33
|
+
stage: str,
|
34
|
+
compute_pool: str,
|
35
|
+
image_repo: str,
|
36
|
+
*,
|
37
|
+
min_instances: int = 1,
|
38
|
+
max_instances: int = 1,
|
39
|
+
endpoint: str = constants.PREDICT,
|
40
|
+
overridden_base_image: Optional[str] = None,
|
41
|
+
prebuilt_snowflake_image: Optional[str] = None,
|
42
|
+
) -> None:
|
43
|
+
"""Initialization
|
44
|
+
|
45
|
+
Args:
|
46
|
+
stage: the name of the stage for uploading artifacts.
|
47
|
+
compute_pool: SnowService compute pool name.
|
48
|
+
image_repo: SnowService image repo path. e.g. "<image_registry>/<db>/<schema>/<repo>"
|
49
|
+
min_instances: Minimum number of service replicas.
|
50
|
+
max_instances: Maximum number of service replicas.
|
51
|
+
endpoint: The specific name of the endpoint that the service function will communicate with. Default to
|
52
|
+
"predict". This option is useful when service has multiple endpoints.
|
53
|
+
overridden_base_image: When provided, it will override the base image.
|
54
|
+
prebuilt_snowflake_image: When provided, the image building step is skipped, and the pre-built image from
|
55
|
+
Snowflake is used as is. This option is for users who consistently use the same image for multiple use
|
56
|
+
cases, allowing faster deployment. The snowflake image used for deployment is logged to the console for
|
57
|
+
future use.
|
58
|
+
"""
|
59
|
+
|
60
|
+
self.stage = stage
|
61
|
+
self.compute_pool = compute_pool
|
62
|
+
self.image_repo = image_repo
|
63
|
+
self.min_instances = min_instances
|
64
|
+
self.max_instances = max_instances
|
65
|
+
self.endpoint = endpoint
|
66
|
+
self.overridden_base_image = overridden_base_image
|
67
|
+
self.prebuilt_snowflake_image = prebuilt_snowflake_image
|
68
|
+
|
69
|
+
@classmethod
|
70
|
+
def from_dict(cls, options_dict: Dict[str, Any]) -> "SnowServiceDeployOptions":
|
71
|
+
"""Construct SnowServiceDeployOptions instance based from an option dictionary.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
options_dict: The dict containing various deployment options.
|
75
|
+
|
76
|
+
Raises:
|
77
|
+
ValueError: When required option is missing.
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
A SnowServiceDeployOptions object
|
81
|
+
"""
|
82
|
+
required_options = [constants.STAGE, constants.COMPUTE_POOL, constants.IMAGE_REPO]
|
83
|
+
missing_keys = [key for key in required_options if options_dict.get(key) is None]
|
84
|
+
if missing_keys:
|
85
|
+
raise ValueError(f"Must provide options when deploying to SnowService: {', '.join(missing_keys)}")
|
86
|
+
# SnowService image repo cannot handle upper case repo name.
|
87
|
+
options_dict[constants.IMAGE_REPO] = options_dict[constants.IMAGE_REPO].lower()
|
88
|
+
return cls(**options_dict)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
spec:
|
2
|
+
container:
|
3
|
+
- name: ${inference_server_container_name}
|
4
|
+
image: ${image}
|
5
|
+
env:
|
6
|
+
MODEL_ZIP_STAGE_PATH: ${model_zip_stage_path}
|
7
|
+
readinessProbe:
|
8
|
+
port: 5000
|
9
|
+
path: /health
|
10
|
+
volumeMounts:
|
11
|
+
- name: vol1
|
12
|
+
mountPath: /local/user/vol1
|
13
|
+
- name: stage
|
14
|
+
mountPath: ${stage}
|
15
|
+
endpoint:
|
16
|
+
- name: ${predict_endpoint_name}
|
17
|
+
port: 5000
|
18
|
+
volume:
|
19
|
+
- name: vol1
|
20
|
+
source: local # only local emptyDir volume is supported
|
21
|
+
- name: stage
|
22
|
+
source: "@${stage}"
|
23
|
+
uid: 1000
|
24
|
+
gid: 1000
|
@@ -0,0 +1,47 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
|
3
|
+
|
4
|
+
class ResourceType(Enum):
|
5
|
+
SERVICE = "service"
|
6
|
+
JOB = "job"
|
7
|
+
|
8
|
+
|
9
|
+
"""
|
10
|
+
Potential SnowService status based on existing ResourceSetStatus proto:
|
11
|
+
|
12
|
+
github.com/snowflakedb/snowflake/blob/main/GlobalServices/src/main/protobuf/snowservices_resourceset_reconciler.proto
|
13
|
+
"""
|
14
|
+
|
15
|
+
|
16
|
+
class ResourceStatus(Enum):
|
17
|
+
UNKNOWN = "UNKNOWN" # status is unknown because we have not received enough data from K8s yet.
|
18
|
+
PENDING = "PENDING" # resource set is being created, can't be used yet
|
19
|
+
READY = "READY" # resource set has been deployed.
|
20
|
+
DELETING = "DELETING" # resource set is being deleted
|
21
|
+
FAILED = "FAILED" # resource set has failed and cannot be used anymore
|
22
|
+
DONE = "DONE" # resource set has finished running
|
23
|
+
NOT_FOUND = "NOT_FOUND" # not found or deleted
|
24
|
+
INTERNAL_ERROR = "INTERNAL_ERROR" # there was an internal service error.
|
25
|
+
|
26
|
+
|
27
|
+
RESOURCE_TO_STATUS_FUNCTION_MAPPING = {
|
28
|
+
ResourceType.SERVICE: "SYSTEM$GET_SNOWSERVICE_STATUS",
|
29
|
+
ResourceType.JOB: "SYSTEM$GET_JOB_STATUS",
|
30
|
+
}
|
31
|
+
|
32
|
+
PREDICT = "predict"
|
33
|
+
STAGE = "stage"
|
34
|
+
COMPUTE_POOL = "compute_pool"
|
35
|
+
IMAGE_REPO = "image_repo"
|
36
|
+
MIN_INSTANCES = "min_instances"
|
37
|
+
MAX_INSTANCES = "max_instances"
|
38
|
+
GPU_COUNT = "gpu"
|
39
|
+
OVERRIDDEN_BASE_IMAGE = "image"
|
40
|
+
ENDPOINT = "endpoint"
|
41
|
+
SERVICE_SPEC = "service_spec"
|
42
|
+
INFERENCE_SERVER_CONTAINER = "inference-server"
|
43
|
+
|
44
|
+
"""Image build related constants"""
|
45
|
+
MODEL_DIR = "model_dir"
|
46
|
+
INFERENCE_SERVER_DIR = "inference_server"
|
47
|
+
ENTRYPOINT_SCRIPT = "gunicorn_run.sh"
|