snowflake-ml-python 1.22.0__py3-none-any.whl → 1.24.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.
Files changed (42) hide show
  1. snowflake/ml/_internal/platform_capabilities.py +0 -4
  2. snowflake/ml/feature_store/__init__.py +2 -0
  3. snowflake/ml/feature_store/aggregation.py +367 -0
  4. snowflake/ml/feature_store/feature.py +366 -0
  5. snowflake/ml/feature_store/feature_store.py +234 -20
  6. snowflake/ml/feature_store/feature_view.py +189 -4
  7. snowflake/ml/feature_store/metadata_manager.py +425 -0
  8. snowflake/ml/feature_store/tile_sql_generator.py +1079 -0
  9. snowflake/ml/jobs/__init__.py +2 -0
  10. snowflake/ml/jobs/_utils/constants.py +1 -0
  11. snowflake/ml/jobs/_utils/payload_utils.py +38 -18
  12. snowflake/ml/jobs/_utils/query_helper.py +8 -1
  13. snowflake/ml/jobs/_utils/runtime_env_utils.py +117 -0
  14. snowflake/ml/jobs/_utils/stage_utils.py +2 -2
  15. snowflake/ml/jobs/_utils/types.py +22 -2
  16. snowflake/ml/jobs/job_definition.py +232 -0
  17. snowflake/ml/jobs/manager.py +16 -177
  18. snowflake/ml/model/__init__.py +4 -0
  19. snowflake/ml/model/_client/model/batch_inference_specs.py +38 -2
  20. snowflake/ml/model/_client/model/model_version_impl.py +120 -89
  21. snowflake/ml/model/_client/ops/model_ops.py +4 -26
  22. snowflake/ml/model/_client/ops/param_utils.py +124 -0
  23. snowflake/ml/model/_client/ops/service_ops.py +63 -23
  24. snowflake/ml/model/_client/service/model_deployment_spec.py +12 -5
  25. snowflake/ml/model/_client/service/model_deployment_spec_schema.py +1 -0
  26. snowflake/ml/model/_client/sql/service.py +25 -54
  27. snowflake/ml/model/_model_composer/model_method/infer_function.py_template +21 -3
  28. snowflake/ml/model/_model_composer/model_method/infer_partitioned.py_template +21 -3
  29. snowflake/ml/model/_model_composer/model_method/infer_table_function.py_template +21 -3
  30. snowflake/ml/model/_model_composer/model_method/model_method.py +3 -1
  31. snowflake/ml/model/_packager/model_handlers/huggingface.py +74 -10
  32. snowflake/ml/model/_packager/model_handlers/sentence_transformers.py +121 -29
  33. snowflake/ml/model/_signatures/utils.py +130 -0
  34. snowflake/ml/model/openai_signatures.py +97 -0
  35. snowflake/ml/registry/_manager/model_parameter_reconciler.py +1 -1
  36. snowflake/ml/version.py +1 -1
  37. {snowflake_ml_python-1.22.0.dist-info → snowflake_ml_python-1.24.0.dist-info}/METADATA +105 -1
  38. {snowflake_ml_python-1.22.0.dist-info → snowflake_ml_python-1.24.0.dist-info}/RECORD +41 -35
  39. {snowflake_ml_python-1.22.0.dist-info → snowflake_ml_python-1.24.0.dist-info}/WHEEL +1 -1
  40. snowflake/ml/experiment/callback/__init__.py +0 -0
  41. {snowflake_ml_python-1.22.0.dist-info → snowflake_ml_python-1.24.0.dist-info}/licenses/LICENSE.txt +0 -0
  42. {snowflake_ml_python-1.22.0.dist-info → snowflake_ml_python-1.24.0.dist-info}/top_level.txt +0 -0
@@ -16,6 +16,7 @@ from snowflake.ml.model._packager.model_meta import (
16
16
  model_meta as model_meta_api,
17
17
  model_meta_schema,
18
18
  )
19
+ from snowflake.ml.model._signatures import utils as model_signature_utils
19
20
  from snowflake.snowpark._internal import utils as snowpark_utils
20
21
 
21
22
  if TYPE_CHECKING:
@@ -23,24 +24,59 @@ if TYPE_CHECKING:
23
24
 
24
25
  logger = logging.getLogger(__name__)
25
26
 
27
+ # Allowlist of supported target methods for SentenceTransformer models.
28
+ # Note: sentence-transformers >= 3.0 uses singular names (encode_query, encode_document)
29
+ # while older versions may use plural names (encode_queries, encode_documents).
30
+ _ALLOWED_TARGET_METHODS = ["encode", "encode_query", "encode_document", "encode_queries", "encode_documents"]
31
+
32
+
33
+ def _validate_sentence_transformers_signatures(
34
+ sigs: dict[str, model_signature.ModelSignature],
35
+ ) -> None:
36
+ """Validate signatures for SentenceTransformer models.
37
+
38
+ Args:
39
+ sigs: Dictionary mapping method names to their signatures.
40
+
41
+ Raises:
42
+ ValueError: If signatures are empty, contain unsupported methods, or violate per-method constraints.
43
+ """
44
+ # Check that signatures are non-empty
45
+ if not sigs:
46
+ raise ValueError("At least one signature must be provided.")
47
+
48
+ # Check that all methods are in the allowlist
49
+ unsupported_methods = set(sigs.keys()) - set(_ALLOWED_TARGET_METHODS)
50
+ if unsupported_methods:
51
+ raise ValueError(
52
+ f"Unsupported target methods: {sorted(unsupported_methods)}. "
53
+ f"Supported methods are: {_ALLOWED_TARGET_METHODS}."
54
+ )
26
55
 
27
- def _validate_sentence_transformers_signatures(sigs: dict[str, model_signature.ModelSignature]) -> None:
28
- if list(sigs.keys()) != ["encode"]:
29
- raise ValueError("target_methods can only be ['encode']")
30
-
31
- if len(sigs["encode"].inputs) != 1:
32
- raise ValueError("SentenceTransformer can only accept 1 input column")
56
+ # Validate per-method constraints
57
+ for method_name, sig in sigs.items():
58
+ if len(sig.inputs) != 1:
59
+ raise ValueError(f"SentenceTransformer method '{method_name}' must have exactly 1 input column.")
33
60
 
34
- if len(sigs["encode"].outputs) != 1:
35
- raise ValueError("SentenceTransformer can only return 1 output column")
61
+ if len(sig.outputs) != 1:
62
+ raise ValueError(f"SentenceTransformer method '{method_name}' must have exactly 1 output column.")
36
63
 
37
- assert isinstance(sigs["encode"].inputs[0], model_signature.FeatureSpec)
64
+ # FeatureSpec is expected here; FeatureGroupSpec would indicate a nested/grouped input
65
+ # which SentenceTransformer does not support.
66
+ if not isinstance(sig.inputs[0], model_signature.FeatureSpec):
67
+ raise ValueError(
68
+ f"SentenceTransformer method '{method_name}' requires a FeatureSpec input, "
69
+ f"got {type(sig.inputs[0]).__name__}."
70
+ )
38
71
 
39
- if sigs["encode"].inputs[0]._shape is not None:
40
- raise ValueError("SentenceTransformer does not support input shape")
72
+ if sig.inputs[0]._shape is not None:
73
+ raise ValueError(f"SentenceTransformer method '{method_name}' does not support input shape.")
41
74
 
42
- if sigs["encode"].inputs[0]._dtype != model_signature.DataType.STRING:
43
- raise ValueError("SentenceTransformer only accepts string input")
75
+ if sig.inputs[0]._dtype != model_signature.DataType.STRING:
76
+ raise ValueError(
77
+ f"SentenceTransformer method '{method_name}' only accepts STRING input, "
78
+ f"got {sig.inputs[0]._dtype.name}."
79
+ )
44
80
 
45
81
 
46
82
  @final
@@ -51,7 +87,9 @@ class SentenceTransformerHandler(_base.BaseModelHandler["sentence_transformers.S
51
87
  _HANDLER_MIGRATOR_PLANS: dict[str, type[base_migrator.BaseModelHandlerMigrator]] = {}
52
88
 
53
89
  MODEL_BLOB_FILE_OR_DIR = "model"
54
- DEFAULT_TARGET_METHODS = ["encode"]
90
+ # Default to singular names which are used in sentence-transformers >= 3.0
91
+ DEFAULT_TARGET_METHODS = ["encode", "encode_query", "encode_document"]
92
+ IS_AUTO_SIGNATURE = True
55
93
 
56
94
  @classmethod
57
95
  def can_handle(
@@ -98,11 +136,17 @@ class SentenceTransformerHandler(_base.BaseModelHandler["sentence_transformers.S
98
136
  target_methods=kwargs.pop("target_methods", None),
99
137
  default_target_methods=cls.DEFAULT_TARGET_METHODS,
100
138
  )
101
- if target_methods != ["encode"]:
102
- raise ValueError("target_methods can only be ['encode']")
139
+
140
+ # Validate target_methods
141
+ if not target_methods:
142
+ raise ValueError("At least one target method must be specified.")
143
+
144
+ if not set(target_methods).issubset(_ALLOWED_TARGET_METHODS):
145
+ raise ValueError(f"target_methods {target_methods} must be a subset of {_ALLOWED_TARGET_METHODS}.")
103
146
 
104
147
  def get_prediction(
105
- target_method_name: str, sample_input_data: model_types.SupportedLocalDataType
148
+ target_method_name: str,
149
+ sample_input_data: model_types.SupportedLocalDataType,
106
150
  ) -> model_types.SupportedLocalDataType:
107
151
  if not isinstance(sample_input_data, pd.DataFrame):
108
152
  sample_input_data = model_signature._convert_local_data_to_df(data=sample_input_data)
@@ -113,8 +157,13 @@ class SentenceTransformerHandler(_base.BaseModelHandler["sentence_transformers.S
113
157
  )
114
158
  X_list = sample_input_data.iloc[:, 0].tolist()
115
159
 
116
- assert callable(getattr(model, "encode", None))
117
- return pd.DataFrame({0: model.encode(X_list, batch_size=batch_size).tolist()})
160
+ # Call the appropriate method based on target_method_name
161
+ method_to_call = getattr(model, target_method_name, None)
162
+ if not callable(method_to_call):
163
+ raise ValueError(
164
+ f"SentenceTransformer model does not have a callable method '{target_method_name}'."
165
+ )
166
+ return pd.DataFrame({0: method_to_call(X_list, batch_size=batch_size).tolist()})
118
167
 
119
168
  if model_meta.signatures:
120
169
  handlers_utils.validate_target_methods(model, list(model_meta.signatures.keys()))
@@ -135,6 +184,36 @@ class SentenceTransformerHandler(_base.BaseModelHandler["sentence_transformers.S
135
184
  sample_input_data=sample_input_data,
136
185
  get_prediction_fn=get_prediction,
137
186
  )
187
+ else:
188
+ # Auto-infer signature from model when no sample_input_data is provided
189
+ # Get the embedding dimension from the model
190
+ embedding_dim = model.get_sentence_embedding_dimension()
191
+ if embedding_dim is None:
192
+ raise ValueError(
193
+ "Unable to auto-infer signature: model.get_sentence_embedding_dimension() returned None. "
194
+ "Please provide sample_input_data or signatures explicitly."
195
+ )
196
+
197
+ for target_method in target_methods:
198
+ # target_methods are already validated as callable by get_target_methods()
199
+ inferred_sig = model_signature_utils.sentence_transformers_signature_auto_infer(
200
+ target_method=target_method,
201
+ embedding_dim=embedding_dim,
202
+ )
203
+ if inferred_sig is None:
204
+ raise ValueError(
205
+ f"Unable to auto-infer signature for method '{target_method}'. "
206
+ "Please provide sample_input_data or signatures explicitly."
207
+ )
208
+ model_meta.signatures[target_method] = inferred_sig
209
+
210
+ # Ensure at least one method was successfully inferred
211
+ if not model_meta.signatures:
212
+ raise ValueError(
213
+ "No valid target methods found on the model. "
214
+ "Please provide sample_input_data or signatures explicitly, "
215
+ "or specify target_methods that exist on your model."
216
+ )
138
217
 
139
218
  _validate_sentence_transformers_signatures(model_meta.signatures)
140
219
 
@@ -160,7 +239,10 @@ class SentenceTransformerHandler(_base.BaseModelHandler["sentence_transformers.S
160
239
 
161
240
  model_meta.env.include_if_absent(
162
241
  [
163
- model_env.ModelDependency(requirement="sentence-transformers", pip_name="sentence-transformers"),
242
+ model_env.ModelDependency(
243
+ requirement="sentence-transformers",
244
+ pip_name="sentence-transformers",
245
+ ),
164
246
  model_env.ModelDependency(requirement="transformers", pip_name="transformers"),
165
247
  model_env.ModelDependency(requirement="pytorch", pip_name="torch"),
166
248
  ],
@@ -169,7 +251,9 @@ class SentenceTransformerHandler(_base.BaseModelHandler["sentence_transformers.S
169
251
  model_meta.env.cuda_version = kwargs.get("cuda_version", handlers_utils.get_default_cuda_version())
170
252
 
171
253
  @staticmethod
172
- def _get_device_config(**kwargs: Unpack[model_types.SentenceTransformersLoadOptions]) -> Optional[str]:
254
+ def _get_device_config(
255
+ **kwargs: Unpack[model_types.SentenceTransformersLoadOptions],
256
+ ) -> Optional[str]:
173
257
  if kwargs.get("device", None) is not None:
174
258
  return kwargs["device"]
175
259
  elif kwargs.get("use_gpu", False):
@@ -226,7 +310,8 @@ class SentenceTransformerHandler(_base.BaseModelHandler["sentence_transformers.S
226
310
  model_meta: model_meta_api.ModelMetadata,
227
311
  ) -> type[custom_model.CustomModel]:
228
312
  batch_size = cast(
229
- model_meta_schema.SentenceTransformersModelBlobOptions, model_meta.models[model_meta.name].options
313
+ model_meta_schema.SentenceTransformersModelBlobOptions,
314
+ model_meta.models[model_meta.name].options,
230
315
  ).get("batch_size", None)
231
316
 
232
317
  def get_prediction(
@@ -234,22 +319,30 @@ class SentenceTransformerHandler(_base.BaseModelHandler["sentence_transformers.S
234
319
  signature: model_signature.ModelSignature,
235
320
  target_method: str,
236
321
  ) -> Callable[[custom_model.CustomModel, pd.DataFrame], pd.DataFrame]:
322
+ # Capture target_method in closure to call the correct model method
323
+ method_to_call = getattr(raw_model, target_method, None)
324
+ if not callable(method_to_call):
325
+ raise ValueError(
326
+ f"SentenceTransformer model does not have a callable method '{target_method}'. "
327
+ f"This method may not be available in your version of sentence-transformers."
328
+ )
329
+
237
330
  @custom_model.inference_api
238
331
  def fn(self: custom_model.CustomModel, X: pd.DataFrame) -> pd.DataFrame:
239
332
  X_list = X.iloc[:, 0].tolist()
240
333
 
241
334
  return pd.DataFrame(
242
- {signature.outputs[0].name: raw_model.encode(X_list, batch_size=batch_size).tolist()}
335
+ {signature.outputs[0].name: method_to_call(X_list, batch_size=batch_size).tolist()}
243
336
  )
244
337
 
245
338
  return fn
246
339
 
247
340
  type_method_dict = {}
248
341
  for target_method_name, sig in model_meta.signatures.items():
249
- if target_method_name == "encode":
342
+ if target_method_name in _ALLOWED_TARGET_METHODS:
250
343
  type_method_dict[target_method_name] = get_prediction(raw_model, sig, target_method_name)
251
344
  else:
252
- ValueError(f"{target_method_name} is currently not supported.")
345
+ raise ValueError(f"{target_method_name} is currently not supported.")
253
346
 
254
347
  _SentenceTransformer = type(
255
348
  "_SentenceTransformer",
@@ -262,7 +355,6 @@ class SentenceTransformerHandler(_base.BaseModelHandler["sentence_transformers.S
262
355
  model = raw_model
263
356
 
264
357
  _SentenceTransformer = _create_custom_model(model, model_meta)
265
- sentence_transformers_SentenceTransformer_model = _SentenceTransformer(custom_model.ModelContext())
266
- predict_method = getattr(sentence_transformers_SentenceTransformer_model, "encode", None)
267
- assert callable(predict_method)
268
- return sentence_transformers_SentenceTransformer_model
358
+ sentence_transformers_model = _SentenceTransformer(custom_model.ModelContext())
359
+
360
+ return sentence_transformers_model
@@ -9,6 +9,7 @@ from snowflake.ml._internal.exceptions import (
9
9
  error_codes,
10
10
  exceptions as snowml_exceptions,
11
11
  )
12
+ from snowflake.ml.model import openai_signatures
12
13
  from snowflake.ml.model._signatures import core
13
14
 
14
15
 
@@ -259,6 +260,66 @@ def huggingface_pipeline_signature_auto_infer(
259
260
  ],
260
261
  )
261
262
 
263
+ # https://huggingface.co/docs/transformers/en/main_classes/pipelines#transformers.ImageClassificationPipeline
264
+ if task == "image-classification":
265
+ return core.ModelSignature(
266
+ inputs=[
267
+ core.FeatureSpec(name="images", dtype=core.DataType.BYTES),
268
+ ],
269
+ outputs=[
270
+ core.FeatureGroupSpec(
271
+ name="labels",
272
+ specs=[
273
+ core.FeatureSpec(name="label", dtype=core.DataType.STRING),
274
+ core.FeatureSpec(name="score", dtype=core.DataType.DOUBLE),
275
+ ],
276
+ shape=(-1,),
277
+ ),
278
+ ],
279
+ )
280
+
281
+ # https://huggingface.co/docs/transformers/en/main_classes/pipelines#transformers.AutomaticSpeechRecognitionPipeline
282
+ if task == "automatic-speech-recognition":
283
+ return core.ModelSignature(
284
+ inputs=[
285
+ core.FeatureSpec(name="audio", dtype=core.DataType.BYTES),
286
+ ],
287
+ outputs=[
288
+ core.FeatureGroupSpec(
289
+ name="outputs",
290
+ specs=[
291
+ core.FeatureSpec(name="text", dtype=core.DataType.STRING),
292
+ core.FeatureGroupSpec(
293
+ name="chunks",
294
+ specs=[
295
+ core.FeatureSpec(name="timestamp", dtype=core.DataType.DOUBLE, shape=(2,)),
296
+ core.FeatureSpec(name="text", dtype=core.DataType.STRING),
297
+ ],
298
+ shape=(-1,), # Variable length list of chunks
299
+ ),
300
+ ],
301
+ )
302
+ ],
303
+ )
304
+
305
+ # https://huggingface.co/docs/transformers/en/main_classes/pipelines#transformers.VideoClassificationPipeline
306
+ if task == "video-classification":
307
+ return core.ModelSignature(
308
+ inputs=[
309
+ core.FeatureSpec(name="video", dtype=core.DataType.BYTES),
310
+ ],
311
+ outputs=[
312
+ core.FeatureGroupSpec(
313
+ name="labels",
314
+ specs=[
315
+ core.FeatureSpec(name="label", dtype=core.DataType.STRING),
316
+ core.FeatureSpec(name="score", dtype=core.DataType.DOUBLE),
317
+ ],
318
+ shape=(-1,),
319
+ ),
320
+ ],
321
+ )
322
+
262
323
  # https://huggingface.co/docs/transformers/en/main_classes/pipelines#transformers.TextGenerationPipeline
263
324
  if task == "text-generation":
264
325
  if params.get("return_tensors", False):
@@ -288,6 +349,22 @@ def huggingface_pipeline_signature_auto_infer(
288
349
  )
289
350
  ],
290
351
  )
352
+
353
+ # https://huggingface.co/docs/transformers/en/main_classes/pipelines#transformers.ImageTextToTextPipeline
354
+ if task in [
355
+ "image-text-to-text",
356
+ "video-text-to-text",
357
+ "audio-text-to-text",
358
+ ]:
359
+ if params.get("return_tensors", False):
360
+ raise NotImplementedError(
361
+ f"Auto deployment for HuggingFace pipeline {task} "
362
+ "when `return_tensors` set to `True` has not been supported yet."
363
+ )
364
+ # Always generate a dict per input
365
+ # defaulting to OPENAI_CHAT_SIGNATURE_SPEC for image-text-to-text pipeline
366
+ return openai_signatures._OPENAI_CHAT_SIGNATURE_SPEC
367
+
291
368
  # https://huggingface.co/docs/transformers/en/main_classes/pipelines#transformers.Text2TextGenerationPipeline
292
369
  if task == "text2text-generation":
293
370
  if params.get("return_tensors", False):
@@ -406,3 +483,56 @@ def infer_dict(name: str, data: dict[str, Any]) -> core.FeatureGroupSpec:
406
483
 
407
484
  def check_if_series_is_empty(series: Optional[pd.Series]) -> bool:
408
485
  return series is None or series.empty
486
+
487
+
488
+ def sentence_transformers_signature_auto_infer(
489
+ target_method: str,
490
+ embedding_dim: int,
491
+ ) -> Optional[core.ModelSignature]:
492
+ """Auto-infer signature for SentenceTransformer models.
493
+
494
+ SentenceTransformer models have a simple signature: they take a string input
495
+ and return an embedding vector (array of floats).
496
+
497
+ Args:
498
+ target_method: The target method name. Supported methods:
499
+ - "encode": General encoding method
500
+ - "encode_query" / "encode_queries": Query encoding for asymmetric search
501
+ - "encode_document" / "encode_documents": Document encoding for asymmetric search
502
+ embedding_dim: The dimension of the embedding vector output by the model.
503
+
504
+ Returns:
505
+ A ModelSignature for the target method, or None if the method is not supported.
506
+
507
+ Note:
508
+ sentence-transformers >= 3.0 uses singular names (encode_query, encode_document)
509
+ while older versions may use plural names (encode_queries, encode_documents).
510
+ Both naming conventions are supported for backward compatibility.
511
+ """
512
+ # Support both singular (new) and plural (old) naming conventions
513
+ supported_methods = [
514
+ "encode",
515
+ "encode_query",
516
+ "encode_document",
517
+ "encode_queries",
518
+ "encode_documents",
519
+ ]
520
+
521
+ if target_method not in supported_methods:
522
+ return None
523
+
524
+ # All SentenceTransformer encode methods have the same signature pattern:
525
+ # - Input: a single string column
526
+ # - Output: a single column containing embedding vectors (array of floats)
527
+ return core.ModelSignature(
528
+ inputs=[
529
+ core.FeatureSpec(name="text", dtype=core.DataType.STRING),
530
+ ],
531
+ outputs=[
532
+ core.FeatureSpec(
533
+ name="output",
534
+ dtype=core.DataType.DOUBLE,
535
+ shape=(embedding_dim,),
536
+ ),
537
+ ],
538
+ )
@@ -1,6 +1,94 @@
1
1
  from snowflake.ml.model._signatures import core
2
2
 
3
3
  _OPENAI_CHAT_SIGNATURE_SPEC = core.ModelSignature(
4
+ inputs=[
5
+ core.FeatureGroupSpec(
6
+ name="messages",
7
+ specs=[
8
+ core.FeatureGroupSpec(
9
+ name="content",
10
+ specs=[
11
+ core.FeatureSpec(name="type", dtype=core.DataType.STRING),
12
+ # Text prompts
13
+ core.FeatureSpec(name="text", dtype=core.DataType.STRING),
14
+ # Image URL prompts
15
+ core.FeatureGroupSpec(
16
+ name="image_url",
17
+ specs=[
18
+ # Base64 encoded image URL or image URL
19
+ core.FeatureSpec(name="url", dtype=core.DataType.STRING),
20
+ # Image detail level (e.g., "low", "high", "auto")
21
+ core.FeatureSpec(name="detail", dtype=core.DataType.STRING),
22
+ ],
23
+ ),
24
+ # Video URL prompts
25
+ core.FeatureGroupSpec(
26
+ name="video_url",
27
+ specs=[
28
+ # Base64 encoded video URL
29
+ core.FeatureSpec(name="url", dtype=core.DataType.STRING),
30
+ ],
31
+ ),
32
+ # Audio prompts
33
+ core.FeatureGroupSpec(
34
+ name="input_audio",
35
+ specs=[
36
+ core.FeatureSpec(name="data", dtype=core.DataType.STRING),
37
+ core.FeatureSpec(name="format", dtype=core.DataType.STRING),
38
+ ],
39
+ ),
40
+ ],
41
+ shape=(-1,),
42
+ ),
43
+ core.FeatureSpec(name="name", dtype=core.DataType.STRING),
44
+ core.FeatureSpec(name="role", dtype=core.DataType.STRING),
45
+ core.FeatureSpec(name="title", dtype=core.DataType.STRING),
46
+ ],
47
+ shape=(-1,),
48
+ ),
49
+ core.FeatureSpec(name="temperature", dtype=core.DataType.DOUBLE),
50
+ core.FeatureSpec(name="max_completion_tokens", dtype=core.DataType.INT64),
51
+ core.FeatureSpec(name="stop", dtype=core.DataType.STRING, shape=(-1,)),
52
+ core.FeatureSpec(name="n", dtype=core.DataType.INT32),
53
+ core.FeatureSpec(name="stream", dtype=core.DataType.BOOL),
54
+ core.FeatureSpec(name="top_p", dtype=core.DataType.DOUBLE),
55
+ core.FeatureSpec(name="frequency_penalty", dtype=core.DataType.DOUBLE),
56
+ core.FeatureSpec(name="presence_penalty", dtype=core.DataType.DOUBLE),
57
+ ],
58
+ outputs=[
59
+ core.FeatureSpec(name="id", dtype=core.DataType.STRING),
60
+ core.FeatureSpec(name="object", dtype=core.DataType.STRING),
61
+ core.FeatureSpec(name="created", dtype=core.DataType.FLOAT),
62
+ core.FeatureSpec(name="model", dtype=core.DataType.STRING),
63
+ core.FeatureGroupSpec(
64
+ name="choices",
65
+ specs=[
66
+ core.FeatureSpec(name="index", dtype=core.DataType.INT32),
67
+ core.FeatureGroupSpec(
68
+ name="message",
69
+ specs=[
70
+ core.FeatureSpec(name="content", dtype=core.DataType.STRING),
71
+ core.FeatureSpec(name="name", dtype=core.DataType.STRING),
72
+ core.FeatureSpec(name="role", dtype=core.DataType.STRING),
73
+ ],
74
+ ),
75
+ core.FeatureSpec(name="logprobs", dtype=core.DataType.STRING),
76
+ core.FeatureSpec(name="finish_reason", dtype=core.DataType.STRING),
77
+ ],
78
+ shape=(-1,),
79
+ ),
80
+ core.FeatureGroupSpec(
81
+ name="usage",
82
+ specs=[
83
+ core.FeatureSpec(name="completion_tokens", dtype=core.DataType.INT32),
84
+ core.FeatureSpec(name="prompt_tokens", dtype=core.DataType.INT32),
85
+ core.FeatureSpec(name="total_tokens", dtype=core.DataType.INT32),
86
+ ],
87
+ ),
88
+ ],
89
+ )
90
+
91
+ _OPENAI_CHAT_SIGNATURE_SPEC_WITH_CONTENT_FORMAT_STRING = core.ModelSignature(
4
92
  inputs=[
5
93
  core.FeatureGroupSpec(
6
94
  name="messages",
@@ -54,4 +142,13 @@ _OPENAI_CHAT_SIGNATURE_SPEC = core.ModelSignature(
54
142
  ],
55
143
  )
56
144
 
145
+
146
+ # Refer vLLM documentation: https://docs.vllm.ai/en/stable/serving/openai_compatible_server/#chat-template
147
+
148
+ # Use this if you prefer to use the content format string instead of the default ChatML template.
149
+ # Most models support this.
150
+ OPENAI_CHAT_SIGNATURE_WITH_CONTENT_FORMAT_STRING = {"__call__": _OPENAI_CHAT_SIGNATURE_SPEC_WITH_CONTENT_FORMAT_STRING}
151
+
152
+ # This is the default signature.
153
+ # The content format allows vLLM to handler content parts like text, image, video, audio, file, etc.
57
154
  OPENAI_CHAT_SIGNATURE = {"__call__": _OPENAI_CHAT_SIGNATURE_SPEC}
@@ -126,7 +126,7 @@ class ModelParameterReconciler:
126
126
  # Default the target platform to SPCS if not specified when running in ML runtime
127
127
  if env.IN_ML_RUNTIME:
128
128
  logger.info(
129
- "Logging the model on Container Runtime for ML without specifying `target_platforms`. "
129
+ "Logging the model on Container Runtime without specifying `target_platforms`. "
130
130
  'Default to `target_platforms=["SNOWPARK_CONTAINER_SERVICES"]`.'
131
131
  )
132
132
  return [target_platform.TargetPlatform.SNOWPARK_CONTAINER_SERVICES]
snowflake/ml/version.py CHANGED
@@ -1,2 +1,2 @@
1
1
  # This is parsed by regex in conda recipe meta file. Make sure not to break it.
2
- VERSION = "1.22.0"
2
+ VERSION = "1.24.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: snowflake-ml-python
3
- Version: 1.22.0
3
+ Version: 1.24.0
4
4
  Summary: The machine learning client library that is used for interacting with Snowflake to build machine learning solutions.
5
5
  Author-email: "Snowflake, Inc" <support@snowflake.com>
6
6
  License:
@@ -417,6 +417,60 @@ NOTE: Version 1.7.0 is used as example here. Please choose the the latest versio
417
417
 
418
418
  # Release History
419
419
 
420
+ ## 1.24.0
421
+
422
+ ### New Features
423
+
424
+ * Feature Store: Added tile-based aggregation support with a new `Feature` API for efficient and
425
+ point-in-time correct time-series feature computation using pre-computed tiles stored in Dynamic Tables.
426
+
427
+ * Registry: Added auto-signature inference for SentenceTransformer models. When logging a SentenceTransformer
428
+ model, `sample_input_data` is now optional. If not provided, the signature is automatically inferred from
429
+ the model's embedding dimension. Supported methods: `encode`, `encode_query`, `encode_document`,
430
+ `encode_queries`, `encode_documents`.
431
+
432
+ ```python
433
+ import sentence_transformers
434
+ from snowflake.ml.registry import Registry
435
+
436
+ # Create model
437
+ model = sentence_transformers.SentenceTransformer("all-MiniLM-L6-v2")
438
+
439
+ # Log model without sample_input_data - signature is auto-inferred
440
+ registry = Registry(session)
441
+ mv = registry.log_model(
442
+ model=model,
443
+ model_name="my_sentence_transformer",
444
+ version_name="v1",
445
+ )
446
+
447
+ # Run inference with auto-inferred signature (input: "text", output: "output")
448
+ import pandas as pd
449
+ result = mv.run(pd.DataFrame({"text": ["Hello world"]}))
450
+ ```
451
+
452
+ ### Bug Fixes
453
+
454
+ ### Behavior Changes
455
+
456
+ ### Deprecations
457
+
458
+ ## 1.23.0
459
+
460
+ ### New Features
461
+
462
+ * ML Jobs: Enabled support for Python 3.11 and Python 3.12 by default. Jobs will automatically select a
463
+ runtime environment matching the client Python version.
464
+
465
+ ### Bug Fixes
466
+
467
+ * Registry: Fix failures on empty output in HuggingFace's Token Classification (aka Named Entity Recognition) models.
468
+ * Model serving: don't parse instance or container statuses to fix bug with missing container status.
469
+
470
+ ### Behavior Changes
471
+
472
+ ### Deprecations
473
+
420
474
  ## 1.22.0
421
475
 
422
476
  ### New Features
@@ -439,10 +493,60 @@ mv = registry.log_model(
439
493
  )
440
494
  ```
441
495
 
496
+ * Registry: Added support for `image-text-to-text` task type in `huggingface.TransformersPipeline`.
497
+ Note: Requires vLLM inference engine while creating the service.
498
+
442
499
  ### Bug Fixes
443
500
 
444
501
  ### Behavior Changes
445
502
 
503
+ * Registry: The `openai_signatures.OPENAI_CHAT_SIGNATURE` signature now handles content parts and
504
+ requires input data to be passed in list of dictionary. To use string content (previous behavior),
505
+ use `openai_signatures.OPENAI_CHAT_SIGNATURE_WITH_CONTENT_FORMAT_STRING`.
506
+
507
+ ```python
508
+ from snowflake.ml.model import openai_signatures
509
+ import pandas as pd
510
+
511
+ mv = snowflake_registry.log_model(
512
+ model=generator,
513
+ model_name=...,
514
+ ...,
515
+ signatures=openai_signatures.OPENAI_CHAT_SIGNATURE,
516
+ )
517
+
518
+ # create a pd.DataFrame with openai.client.chat.completions arguments like below:
519
+ x_df = pd.DataFrame.from_records(
520
+ [
521
+ {
522
+ "messages": [
523
+ {"role": "system", "content": "Complete the sentence."},
524
+ {
525
+ "role": "user",
526
+ "content": [
527
+ {
528
+ "type": "text",
529
+ "text": "A descendant of the Lost City of Atlantis, who swam to Earth while saying, ",
530
+ }
531
+ ],
532
+ },
533
+ ],
534
+ "max_completion_tokens": 250,
535
+ "temperature": 0.9,
536
+ "stop": None,
537
+ "n": 3,
538
+ "stream": False,
539
+ "top_p": 1.0,
540
+ "frequency_penalty": 0.1,
541
+ "presence_penalty": 0.2,
542
+ }
543
+ ],
544
+ )
545
+
546
+ # OpenAI Chat Completion compatible output
547
+ output_df = mv.run(X=x_df)
548
+ ```
549
+
446
550
  ### Deprecations
447
551
 
448
552
  ## 1.21.0