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,178 @@
|
|
1
|
+
import json
|
2
|
+
import logging
|
3
|
+
import time
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
from snowflake.ml.model._deploy_client.utils import constants
|
7
|
+
from snowflake.snowpark import Session
|
8
|
+
|
9
|
+
|
10
|
+
class SnowServiceClient:
|
11
|
+
"""
|
12
|
+
SnowService client implementation: a Python wrapper for SnowService SQL queries.
|
13
|
+
"""
|
14
|
+
|
15
|
+
def __init__(self, session: Session) -> None:
|
16
|
+
"""Initialization
|
17
|
+
|
18
|
+
Args:
|
19
|
+
session: Snowpark session
|
20
|
+
"""
|
21
|
+
self.session = session
|
22
|
+
|
23
|
+
def create_or_replace_service(
|
24
|
+
self,
|
25
|
+
service_name: str,
|
26
|
+
compute_pool: str,
|
27
|
+
spec_stage_location: str,
|
28
|
+
*,
|
29
|
+
min_instances: int = 1,
|
30
|
+
max_instances: int = 1,
|
31
|
+
) -> None:
|
32
|
+
"""Create or replace service. Since SnowService doesn't support the CREATE OR REPLACE service syntax, we will
|
33
|
+
first attempt to drop the service if it exists, and then create the service. Please note that this approach may
|
34
|
+
have side effects due to the lack of transaction support.
|
35
|
+
|
36
|
+
Args:
|
37
|
+
service_name: Name of the service.
|
38
|
+
min_instances: Minimum number of service replicas.
|
39
|
+
max_instances: Maximum number of service replicas.
|
40
|
+
compute_pool: Name of the compute pool.
|
41
|
+
spec_stage_location: Stage path for the service spec.
|
42
|
+
"""
|
43
|
+
self._drop_service_if_exists(service_name)
|
44
|
+
sql = f"""
|
45
|
+
CREATE SERVICE {service_name}
|
46
|
+
MIN_INSTANCES={min_instances}
|
47
|
+
MAX_INSTANCES={max_instances}
|
48
|
+
COMPUTE_POOL={compute_pool}
|
49
|
+
SPEC=@{spec_stage_location}
|
50
|
+
"""
|
51
|
+
logging.info(f"Create service with SQL: \n {sql}")
|
52
|
+
self.session.sql(sql).collect()
|
53
|
+
|
54
|
+
def _drop_service_if_exists(self, service_name: str) -> None:
|
55
|
+
"""Drop service if it already exists.
|
56
|
+
|
57
|
+
Args:
|
58
|
+
service_name: Name of the service.
|
59
|
+
"""
|
60
|
+
self.session.sql(f"DROP SERVICE IF EXISTS {service_name}").collect()
|
61
|
+
|
62
|
+
def create_or_replace_service_function(
|
63
|
+
self,
|
64
|
+
service_func_name: str,
|
65
|
+
service_name: str,
|
66
|
+
*,
|
67
|
+
endpoint_name: str = constants.PREDICT,
|
68
|
+
path_at_service_endpoint: str = constants.PREDICT,
|
69
|
+
) -> None:
|
70
|
+
"""Create or replace service function.
|
71
|
+
|
72
|
+
Args:
|
73
|
+
service_func_name: Name of the service function.
|
74
|
+
service_name: Name of the service.
|
75
|
+
endpoint_name: Name the service endpoint, declared in the service spec, indicating the listening port.
|
76
|
+
path_at_service_endpoint: Specify the path/route at the service endpoint. Multiple paths can exist for a
|
77
|
+
given endpoint. For example, an inference server listening on port 5000 may have paths like "/predict"
|
78
|
+
and "/monitoring
|
79
|
+
|
80
|
+
"""
|
81
|
+
sql = f"""
|
82
|
+
CREATE OR REPLACE FUNCTION {service_func_name}(input OBJECT)
|
83
|
+
RETURNS OBJECT
|
84
|
+
SERVICE={service_name}
|
85
|
+
ENDPOINT={endpoint_name}
|
86
|
+
AS '/{path_at_service_endpoint}'
|
87
|
+
"""
|
88
|
+
logging.info(f"Create service function with SQL: \n {sql}")
|
89
|
+
self.session.sql(sql).collect()
|
90
|
+
|
91
|
+
def block_until_resource_is_ready(
|
92
|
+
self,
|
93
|
+
resource_name: str,
|
94
|
+
resource_type: constants.ResourceType,
|
95
|
+
*,
|
96
|
+
max_retries: int = 60,
|
97
|
+
retry_interval_secs: int = 5,
|
98
|
+
) -> None:
|
99
|
+
"""Blocks execution until the specified resource is ready.
|
100
|
+
Note that this is a best-effort approach because when launching a service, it's possible for it to initially
|
101
|
+
fail due to a system error. However, SnowService may automatically retry and recover the service, leading to
|
102
|
+
potential false-negative information.
|
103
|
+
|
104
|
+
Args:
|
105
|
+
resource_name: Name of the resource.
|
106
|
+
resource_type: Type of the resource.
|
107
|
+
max_retries: The maximum number of retries to check the resource readiness (default: 60).
|
108
|
+
retry_interval_secs: The number of seconds to wait between each retry (default: 5).
|
109
|
+
|
110
|
+
Raises:
|
111
|
+
RuntimeError: If the resource received the following status [failed, not_found, internal_error, deleting]
|
112
|
+
RuntimeError: If the resource does not reach the ready/done state within the specified number of retries.
|
113
|
+
"""
|
114
|
+
for _ in range(max_retries):
|
115
|
+
status = self.get_resource_status(resource_name=resource_name, resource_type=resource_type)
|
116
|
+
if status in [constants.ResourceStatus.READY, constants.ResourceStatus.DONE]:
|
117
|
+
return
|
118
|
+
elif status in [
|
119
|
+
constants.ResourceStatus.FAILED,
|
120
|
+
constants.ResourceStatus.NOT_FOUND,
|
121
|
+
constants.ResourceStatus.INTERNAL_ERROR,
|
122
|
+
constants.ResourceStatus.DELETING,
|
123
|
+
]:
|
124
|
+
error_log = self.get_resource_log(
|
125
|
+
resource_name=resource_name,
|
126
|
+
resource_type=resource_type,
|
127
|
+
container_name=constants.INFERENCE_SERVER_CONTAINER,
|
128
|
+
)
|
129
|
+
raise RuntimeError(f"{resource_type} {resource_name} failed. \n {error_log if error_log else ''}")
|
130
|
+
time.sleep(retry_interval_secs)
|
131
|
+
|
132
|
+
raise RuntimeError("Resource never reached the ready/done state.")
|
133
|
+
|
134
|
+
def get_resource_log(
|
135
|
+
self, resource_name: str, resource_type: constants.ResourceType, container_name: str
|
136
|
+
) -> Optional[str]:
|
137
|
+
if resource_type != constants.ResourceType.SERVICE:
|
138
|
+
raise NotImplementedError(f"{resource_type.name} is not yet supported in get_resource_log function")
|
139
|
+
try:
|
140
|
+
row = self.session.sql(
|
141
|
+
f"CALL SYSTEM$GET_SNOWSERVICE_LOGS('{resource_name}', '0', '{container_name}')"
|
142
|
+
).collect()
|
143
|
+
return str(row[0]["SYSTEM$GET_SNOWSERVICE_LOGS"])
|
144
|
+
except Exception:
|
145
|
+
return None
|
146
|
+
|
147
|
+
def get_resource_status(
|
148
|
+
self, resource_name: str, resource_type: constants.ResourceType
|
149
|
+
) -> Optional[constants.ResourceStatus]:
|
150
|
+
"""Get resource status.
|
151
|
+
|
152
|
+
Args:
|
153
|
+
resource_name: Name of the resource.
|
154
|
+
resource_type: Type of the resource.
|
155
|
+
|
156
|
+
Raises:
|
157
|
+
ValueError: If resource type does not have a corresponding system function for querying status.
|
158
|
+
RuntimeError: If corresponding status call failed.
|
159
|
+
|
160
|
+
Returns:
|
161
|
+
Optional[constants.ResourceStatus]: The status of the resource, or None if the resource status is empty.
|
162
|
+
"""
|
163
|
+
if resource_type not in constants.RESOURCE_TO_STATUS_FUNCTION_MAPPING:
|
164
|
+
raise ValueError(f"Status querying is not supported for resources of type '{resource_type}'.")
|
165
|
+
status_func = constants.RESOURCE_TO_STATUS_FUNCTION_MAPPING[resource_type]
|
166
|
+
try:
|
167
|
+
row = self.session.sql(f"CALL {status_func}('{resource_name}');").collect()
|
168
|
+
except Exception as e:
|
169
|
+
raise RuntimeError(f"Error while querying the {resource_type} {resource_name} status: {str(e)}")
|
170
|
+
resource_metadata = json.loads(row[0][status_func])[0]
|
171
|
+
logging.info(f"Resource status metadata: {resource_metadata}")
|
172
|
+
if resource_metadata and resource_metadata["status"]:
|
173
|
+
try:
|
174
|
+
status = resource_metadata["status"]
|
175
|
+
return constants.ResourceStatus(status)
|
176
|
+
except ValueError:
|
177
|
+
logging.warning(f"Unknown status returned: {status}")
|
178
|
+
return None
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import os
|
2
|
+
import posixpath
|
2
3
|
import tempfile
|
3
4
|
import warnings
|
4
5
|
from types import ModuleType
|
@@ -6,7 +7,8 @@ from typing import IO, List, Optional, Tuple, TypedDict, Union
|
|
6
7
|
|
7
8
|
from typing_extensions import Unpack
|
8
9
|
|
9
|
-
from snowflake.ml._internal import
|
10
|
+
from snowflake.ml._internal import env_utils, file_utils
|
11
|
+
from snowflake.ml._internal.utils import identifier
|
10
12
|
from snowflake.ml.model import (
|
11
13
|
_env as model_env,
|
12
14
|
_model,
|
@@ -37,6 +39,7 @@ def _deploy_to_warehouse(
|
|
37
39
|
**kwargs: Options that control some features in generated udf code.
|
38
40
|
|
39
41
|
Raises:
|
42
|
+
ValueError: Raised when model file name is unable to encoded using ASCII.
|
40
43
|
ValueError: Raised when incompatible model.
|
41
44
|
ValueError: Raised when target method does not exist in model.
|
42
45
|
ValueError: Raised when confronting invalid stage location.
|
@@ -44,14 +47,20 @@ def _deploy_to_warehouse(
|
|
44
47
|
Returns:
|
45
48
|
The metadata of the model deployed.
|
46
49
|
"""
|
50
|
+
# TODO(SNOW-862576): Should remove check on ASCII encoding after SNOW-862576 fixed.
|
47
51
|
if model_dir_path:
|
48
52
|
model_dir_path = os.path.normpath(model_dir_path)
|
49
53
|
model_dir_name = os.path.basename(model_dir_path)
|
54
|
+
if not file_utils._able_ascii_encode(model_dir_name):
|
55
|
+
raise ValueError(f"Model file name {model_dir_name} cannot be encoded using ASCII. Please rename.")
|
50
56
|
extract_model_code = infer_template._EXTRACT_LOCAL_MODEL_CODE.format(model_dir_name=model_dir_name)
|
51
57
|
meta = _model.load_model(model_dir_path=model_dir_path, meta_only=True)
|
52
58
|
else:
|
53
59
|
assert model_stage_file_path is not None, "Unreachable assertion error."
|
54
|
-
model_stage_file_name =
|
60
|
+
model_stage_file_name = posixpath.basename(model_stage_file_path)
|
61
|
+
if not file_utils._able_ascii_encode(model_stage_file_name):
|
62
|
+
raise ValueError(f"Model file name {model_stage_file_name} cannot be encoded using ASCII. Please rename.")
|
63
|
+
|
55
64
|
extract_model_code = infer_template._EXTRACT_STAGE_MODEL_CODE.format(
|
56
65
|
model_stage_file_name=model_stage_file_name
|
57
66
|
)
|
@@ -59,32 +68,26 @@ def _deploy_to_warehouse(
|
|
59
68
|
|
60
69
|
relax_version = kwargs.get("relax_version", False)
|
61
70
|
|
71
|
+
disable_local_conda_resolver = kwargs.get("disable_local_conda_resolver", False)
|
72
|
+
|
62
73
|
if target_method not in meta.signatures.keys():
|
63
74
|
raise ValueError(f"Target method {target_method} does not exist in model.")
|
64
75
|
|
65
|
-
_use_local_snowml = kwargs.get("_use_local_snowml", False)
|
66
|
-
|
67
76
|
final_packages = _get_model_final_packages(
|
68
|
-
meta, session, relax_version=relax_version,
|
77
|
+
meta, session, relax_version=relax_version, disable_local_conda_resolver=disable_local_conda_resolver
|
69
78
|
)
|
70
79
|
|
71
80
|
stage_location = kwargs.get("permanent_udf_stage_location", None)
|
72
81
|
if stage_location:
|
73
|
-
stage_location = stage_location.strip()
|
82
|
+
stage_location = posixpath.normpath(stage_location.strip())
|
74
83
|
if not stage_location.startswith("@"):
|
75
84
|
raise ValueError(f"Invalid stage location {stage_location}.")
|
76
85
|
|
77
|
-
|
78
|
-
if _use_local_snowml:
|
79
|
-
_snowml_wheel_path = file_utils.upload_snowml(session, stage_location=stage_location)
|
80
|
-
|
81
|
-
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
|
86
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False, encoding="utf-8") as f:
|
82
87
|
_write_UDF_py_file(f.file, extract_model_code, target_method, **kwargs)
|
83
88
|
print(f"Generated UDF file is persisted at: {f.name}")
|
84
|
-
imports = (
|
85
|
-
|
86
|
-
+ ([model_stage_file_path] if model_stage_file_path else [])
|
87
|
-
+ ([_snowml_wheel_path] if _snowml_wheel_path else [])
|
89
|
+
imports = ([model_dir_path] if model_dir_path else []) + (
|
90
|
+
[model_stage_file_path] if model_stage_file_path else []
|
88
91
|
)
|
89
92
|
|
90
93
|
class _UDFParams(TypedDict):
|
@@ -99,7 +102,7 @@ def _deploy_to_warehouse(
|
|
99
102
|
params = _UDFParams(
|
100
103
|
file_path=f.name,
|
101
104
|
func_name="infer",
|
102
|
-
name=
|
105
|
+
name=identifier.get_inferred_name(udf_name),
|
103
106
|
return_type=st.PandasSeriesType(st.MapType(st.StringType(), st.VariantType())),
|
104
107
|
input_types=[st.PandasDataFrameType([st.MapType()])],
|
105
108
|
imports=list(imports),
|
@@ -139,6 +142,7 @@ def _write_UDF_py_file(
|
|
139
142
|
extract_model_code=extract_model_code,
|
140
143
|
keep_order_code=infer_template._KEEP_ORDER_CODE_TEMPLATE if keep_order else "",
|
141
144
|
target_method=target_method,
|
145
|
+
code_dir_name=_model_meta.ModelMetadata.MODEL_CODE_DIR,
|
142
146
|
)
|
143
147
|
f.write(udf_code)
|
144
148
|
f.flush()
|
@@ -148,7 +152,7 @@ def _get_model_final_packages(
|
|
148
152
|
meta: _model_meta.ModelMetadata,
|
149
153
|
session: snowpark_session.Session,
|
150
154
|
relax_version: Optional[bool] = False,
|
151
|
-
|
155
|
+
disable_local_conda_resolver: Optional[bool] = False,
|
152
156
|
) -> List[str]:
|
153
157
|
"""Generate final packages list of dependency of a model to be deployed to warehouse.
|
154
158
|
|
@@ -157,7 +161,8 @@ def _get_model_final_packages(
|
|
157
161
|
session: Snowpark connection session.
|
158
162
|
relax_version: Whether or not relax the version restriction when fail to resolve dependencies.
|
159
163
|
Defaults to False.
|
160
|
-
|
164
|
+
disable_local_conda_resolver: Set to disable use local conda resolver to do pre-check on environment and rely on
|
165
|
+
the information schema only. Defaults to False.
|
161
166
|
|
162
167
|
Raises:
|
163
168
|
RuntimeError: Raised when PIP requirements and dependencies from non-Snowflake anaconda channel found.
|
@@ -174,18 +179,10 @@ def _get_model_final_packages(
|
|
174
179
|
raise RuntimeError("PIP requirements and dependencies from non-Snowflake anaconda channel is not supported.")
|
175
180
|
|
176
181
|
deps = meta._conda_dependencies[""]
|
177
|
-
if _use_local_snowml:
|
178
|
-
local_snowml_version = snowml_env.VERSION
|
179
|
-
snowml_dept = next((dep for dep in deps if dep.name == env_utils._SNOWML_PKG_NAME), None)
|
180
|
-
if snowml_dept:
|
181
|
-
if not snowml_dept.specifier.contains(local_snowml_version) and not relax_version:
|
182
|
-
raise RuntimeError(
|
183
|
-
"Incompatible snowflake-ml-python-version is found. "
|
184
|
-
+ f"Require {snowml_dept.specifier}, got {local_snowml_version}."
|
185
|
-
)
|
186
|
-
deps.remove(snowml_dept)
|
187
182
|
|
188
183
|
try:
|
184
|
+
if disable_local_conda_resolver:
|
185
|
+
raise ImportError("Raise to disable local conda resolver. Should be captured.")
|
189
186
|
final_packages = env_utils.resolve_conda_environment(
|
190
187
|
deps, [model_env._SNOWFLAKE_CONDA_CHANNEL_URL], python_version=meta.python_version
|
191
188
|
)
|
@@ -48,18 +48,21 @@ class FileLock:
|
|
48
48
|
IMPORT_DIRECTORY_NAME = "snowflake_import_directory"
|
49
49
|
import_dir = sys._xoptions[IMPORT_DIRECTORY_NAME]
|
50
50
|
|
51
|
-
from snowflake.ml.model import _model
|
52
|
-
|
53
51
|
{extract_model_code}
|
54
52
|
|
53
|
+
sys.path.insert(0, os.path.join(extracted_model_dir_path, "{code_dir_name}"))
|
54
|
+
from snowflake.ml.model import _model
|
55
55
|
model, meta = _model._load_model_for_deploy(extracted_model_dir_path)
|
56
56
|
|
57
|
+
features = meta.signatures["{target_method}"].inputs
|
58
|
+
input_cols = [feature.name for feature in features]
|
59
|
+
dtype_map = {{feature.name: feature.as_dtype() for feature in features}}
|
60
|
+
|
57
61
|
# TODO(halu): Wire `max_batch_size`.
|
58
62
|
# TODO(halu): Avoid per batch async detection branching.
|
59
63
|
@vectorized(input=pd.DataFrame, max_batch_size=10)
|
60
64
|
def infer(df):
|
61
|
-
|
62
|
-
input_df = pd.io.json.json_normalize(df[0])
|
65
|
+
input_df = pd.io.json.json_normalize(df[0]).astype(dtype=dtype_map)
|
63
66
|
if inspect.iscoroutinefunction(model.{target_method}):
|
64
67
|
predictions_df = anyio.run(model.{target_method}, input_df[input_cols])
|
65
68
|
else:
|
snowflake/ml/model/_deployer.py
CHANGED
@@ -1,9 +1,7 @@
|
|
1
|
-
import json
|
2
1
|
import traceback
|
3
2
|
from enum import Enum
|
4
3
|
from typing import Optional, TypedDict, Union, overload
|
5
4
|
|
6
|
-
import numpy as np
|
7
5
|
import pandas as pd
|
8
6
|
from typing_extensions import Required
|
9
7
|
|
@@ -184,7 +182,6 @@ def predict(
|
|
184
182
|
|
185
183
|
Raises:
|
186
184
|
ValueError: Raised when the input is too large to use keep_order option.
|
187
|
-
NotImplementedError: FeatureGroupSpec is not supported.
|
188
185
|
|
189
186
|
Returns:
|
190
187
|
The output dataframe.
|
@@ -199,19 +196,19 @@ def predict(
|
|
199
196
|
# Validate and prepare input
|
200
197
|
if not isinstance(X, SnowparkDataFrame):
|
201
198
|
df = model_signature._convert_and_validate_local_data(X, sig.inputs)
|
202
|
-
s_df =
|
199
|
+
s_df = model_signature._SnowparkDataFrameHandler.convert_from_df(session, df, keep_order=keep_order)
|
203
200
|
else:
|
204
201
|
model_signature._validate_snowpark_data(X, sig.inputs)
|
205
202
|
s_df = X
|
206
203
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
204
|
+
if keep_order:
|
205
|
+
# ID is UINT64 type, this we should limit.
|
206
|
+
if s_df.count() > 2**64:
|
207
|
+
raise ValueError("Unable to keep order of a DataFrame with more than 2 ** 64 rows.")
|
208
|
+
s_df = s_df.with_column(
|
209
|
+
infer_template._KEEP_ORDER_COL_NAME,
|
210
|
+
F.monotonically_increasing_id(),
|
211
|
+
)
|
215
212
|
|
216
213
|
# Infer and get intermediate result
|
217
214
|
input_cols = []
|
@@ -223,7 +220,9 @@ def predict(
|
|
223
220
|
F.col(col_name),
|
224
221
|
]
|
225
222
|
)
|
226
|
-
output_obj = F.call_udf(
|
223
|
+
output_obj = F.call_udf(
|
224
|
+
identifier.get_inferred_name(deployment["name"]), F.object_construct(*input_cols) # type:ignore[arg-type]
|
225
|
+
)
|
227
226
|
if output_with_input_features:
|
228
227
|
df_res = s_df.with_column(INTERMEDIATE_OBJ_NAME, output_obj)
|
229
228
|
else:
|
@@ -243,24 +242,12 @@ def predict(
|
|
243
242
|
output_cols.append(F.col(INTERMEDIATE_OBJ_NAME)[output_feature.name].astype(output_feature.as_snowpark_type()))
|
244
243
|
|
245
244
|
df_res = df_res.with_columns(
|
246
|
-
[identifier.
|
245
|
+
[identifier.get_inferred_name(output_feature.name) for output_feature in sig.outputs],
|
247
246
|
output_cols,
|
248
247
|
).drop(INTERMEDIATE_OBJ_NAME)
|
249
248
|
|
250
249
|
# Get final result
|
251
250
|
if not isinstance(X, SnowparkDataFrame):
|
252
|
-
|
253
|
-
for feature in sig.outputs:
|
254
|
-
if isinstance(feature, model_signature.FeatureGroupSpec):
|
255
|
-
raise NotImplementedError("FeatureGroupSpec is not supported.")
|
256
|
-
assert isinstance(feature, model_signature.FeatureSpec), "Invalid feature kind."
|
257
|
-
dtype_map[feature.name] = feature.as_dtype()
|
258
|
-
df_local = df_res.to_pandas()
|
259
|
-
# This is because Array and object will generate variant type and requires an additional loads to
|
260
|
-
# get correct data otherwise it would be string.
|
261
|
-
for col_name in [col_name for col_name, col_dtype in dtype_map.items() if col_dtype == np.object0]:
|
262
|
-
df_local[col_name] = df_local[col_name].map(json.loads)
|
263
|
-
df_local = df_local.astype(dtype=dtype_map)
|
264
|
-
return pd.DataFrame(df_local)
|
251
|
+
return model_signature._SnowparkDataFrameHandler.convert_to_df(df_res, features=sig.outputs)
|
265
252
|
else:
|
266
253
|
return df_res
|
snowflake/ml/model/_env.py
CHANGED
@@ -36,7 +36,7 @@ def save_conda_env_file(
|
|
36
36
|
for chan, reqs in deps.items():
|
37
37
|
env["dependencies"].extend([f"{chan}::{str(req)}" if chan else str(req) for req in reqs])
|
38
38
|
|
39
|
-
with open(path, "w") as f:
|
39
|
+
with open(path, "w", encoding="utf-8") as f:
|
40
40
|
yaml.safe_dump(env, stream=f, default_flow_style=False)
|
41
41
|
|
42
42
|
return path
|
@@ -54,7 +54,7 @@ def save_requirements_file(dir_path: str, pip_deps: List[requirements.Requiremen
|
|
54
54
|
"""
|
55
55
|
requirements = "\n".join(map(str, pip_deps))
|
56
56
|
path = os.path.join(dir_path, _REQUIREMENTS_FILE_NAME)
|
57
|
-
with open(path, "w") as out:
|
57
|
+
with open(path, "w", encoding="utf-8") as out:
|
58
58
|
out.write(requirements)
|
59
59
|
|
60
60
|
return path
|
@@ -69,7 +69,7 @@ def load_conda_env_file(path: str) -> Tuple[DefaultDict[str, List[requirements.R
|
|
69
69
|
Returns:
|
70
70
|
A tuple of Dict of conda dependencies after validated and a string 'major.minor.patchlevel' of python version.
|
71
71
|
"""
|
72
|
-
with open(path) as f:
|
72
|
+
with open(path, encoding="utf-8") as f:
|
73
73
|
env = yaml.safe_load(stream=f)
|
74
74
|
|
75
75
|
assert isinstance(env, dict)
|
@@ -99,7 +99,7 @@ def load_requirements_file(path: str) -> List[requirements.Requirement]:
|
|
99
99
|
Returns:
|
100
100
|
List of dependencies string after validated.
|
101
101
|
"""
|
102
|
-
with open(path) as f:
|
102
|
+
with open(path, encoding="utf-8") as f:
|
103
103
|
reqs = f.readlines()
|
104
104
|
|
105
105
|
return env_utils.validate_pip_requirement_string_list(reqs)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from abc import ABC, abstractmethod
|
2
2
|
from typing import Generic, Optional
|
3
3
|
|
4
|
-
from typing_extensions import TypeGuard
|
4
|
+
from typing_extensions import TypeGuard, Unpack
|
5
5
|
|
6
6
|
from snowflake.ml.model import _model_meta, type_hints as model_types
|
7
7
|
|
@@ -43,6 +43,7 @@ class _ModelHandler(ABC, Generic[model_types._ModelType]):
|
|
43
43
|
model_blobs_dir_path: str,
|
44
44
|
sample_input: Optional[model_types.SupportedDataType] = None,
|
45
45
|
is_sub_model: Optional[bool] = False,
|
46
|
+
**kwargs: Unpack[model_types.ModelSaveOption],
|
46
47
|
) -> None:
|
47
48
|
"""Save the model.
|
48
49
|
|
@@ -53,6 +54,7 @@ class _ModelHandler(ABC, Generic[model_types._ModelType]):
|
|
53
54
|
model_blobs_dir_path: Directory path to the model.
|
54
55
|
sample_input: Sample input to infer the signatures from.
|
55
56
|
is_sub_model: Flag to show if it is a sub model, a sub model does not need signature.
|
57
|
+
kwargs: Additional saving options.
|
56
58
|
"""
|
57
59
|
...
|
58
60
|
|
@@ -1,16 +1,19 @@
|
|
1
1
|
import inspect
|
2
2
|
import os
|
3
|
+
import pathlib
|
3
4
|
import sys
|
4
5
|
from typing import TYPE_CHECKING, Dict, Optional
|
5
6
|
|
6
7
|
import anyio
|
7
8
|
import cloudpickle
|
9
|
+
import pandas as pd
|
8
10
|
from typing_extensions import TypeGuard, Unpack
|
9
11
|
|
10
12
|
from snowflake.ml._internal import file_utils, type_utils
|
11
13
|
from snowflake.ml.model import (
|
12
14
|
_model_handler,
|
13
15
|
_model_meta as model_meta_api,
|
16
|
+
model_signature,
|
14
17
|
type_hints as model_types,
|
15
18
|
)
|
16
19
|
from snowflake.ml.model._handlers import _base
|
@@ -55,6 +58,10 @@ class _CustomModelHandler(_base._ModelHandler["custom_model.CustomModel"]):
|
|
55
58
|
target_method = getattr(model, target_method_name, None)
|
56
59
|
assert callable(target_method) and inspect.ismethod(target_method)
|
57
60
|
target_method = target_method.__func__
|
61
|
+
|
62
|
+
if not isinstance(sample_input, pd.DataFrame):
|
63
|
+
sample_input = model_signature._convert_local_data_to_df(sample_input)
|
64
|
+
|
58
65
|
if inspect.iscoroutinefunction(target_method):
|
59
66
|
with anyio.start_blocking_portal() as portal:
|
60
67
|
predictions_df = portal.call(target_method, model, sample_input)
|
@@ -102,7 +109,9 @@ class _CustomModelHandler(_base._ModelHandler["custom_model.CustomModel"]):
|
|
102
109
|
model_type=_CustomModelHandler.handler_type,
|
103
110
|
path=_CustomModelHandler.MODEL_BLOB_FILE,
|
104
111
|
artifacts={
|
105
|
-
name:
|
112
|
+
name: pathlib.Path(
|
113
|
+
os.path.join(_CustomModelHandler.MODEL_ARTIFACTS_DIR, os.path.basename(os.path.normpath(path=uri)))
|
114
|
+
).as_posix()
|
106
115
|
for name, uri in model.context.artifacts.items()
|
107
116
|
},
|
108
117
|
)
|
@@ -129,7 +138,10 @@ class _CustomModelHandler(_base._ModelHandler["custom_model.CustomModel"]):
|
|
129
138
|
assert issubclass(ModelClass, custom_model.CustomModel)
|
130
139
|
|
131
140
|
artifacts_meta = model_blob_metadata.artifacts
|
132
|
-
artifacts = {
|
141
|
+
artifacts = {
|
142
|
+
name: str(pathlib.PurePath(model_blob_path) / pathlib.PurePosixPath(rel_path))
|
143
|
+
for name, rel_path in artifacts_meta.items()
|
144
|
+
}
|
133
145
|
models: Dict[str, model_types.SupportedModelType] = dict()
|
134
146
|
for sub_model_name, _ref in m.context.model_refs.items():
|
135
147
|
model_type = model_meta.models[sub_model_name].model_type
|