[Support inference params-5]Add docs for inference params#8977
Conversation
|
Documentation preview for eebc0da will be available here when this CircleCI job completes successfully. More info
|
| a shape of ``(-1,)``. If the parameter's type or shape is incompatible, an exception will be raised. | ||
| Additionally, the value of the parameter is validated against the specified type in the signature. We attempt | ||
| to convert the value to the specified type, and if this conversion fails, an MlflowException will be raised. | ||
|
|
There was a problem hiding this comment.
Can we provide a list with generic examples of what parameter types are supported?
Can there also be an explanation added of why this feature exists and how it can be used as an intro to a new section above this? This is a pretty important and long asked-for feature that I think another ^^^^^ section is warranted to explain that inference parameters are supported now :)
Signed-off-by: Serena Ruan <[email protected]>
|
@serena-ruan Can you push a commit? I want to see the doc preview. |
Can I just rerun https://github.com/mlflow/mlflow/actions/runs/5596007324 this job? |
Yes, but I'm not sure if it's rerunable. |
Signed-off-by: Serena Ruan <[email protected]>
| from mlflow.models import infer_signature | ||
| from mlflow.transformers import generate_signature_output | ||
|
|
||
| # Infer the signature | ||
| signature = infer_signature( | ||
| data, | ||
| generate_signature_output(model, data), | ||
| ) | ||
|
|
||
| # Define an inference_config | ||
| inference_config = { | ||
| "num_beams": 5, | ||
| "max_length": 30, | ||
| "do_sample": True, | ||
| "remove_invalid_values": True, | ||
| } | ||
|
|
||
| # Saving inference_config with the model | ||
| mlflow.transformers.save_model( | ||
| model, | ||
| path=model_path, | ||
| inference_config=inference_config, | ||
| signature=signature, | ||
| ) | ||
|
|
||
| pyfunc_loaded = mlflow.pyfunc.load_model(model_path) | ||
| # inference_config will be applied | ||
| result = pyfunc_loaded.predict(data) |
There was a problem hiding this comment.
This example contains undefined variables and modules. Can we add them?
There was a problem hiding this comment.
I think a long complete example is better than a short incomplete example.
| from mlflow.models import infer_signature | ||
| import transformers | ||
|
|
||
| architecture = "mrm8488/t5-base-finetuned-common_gen" |
Signed-off-by: Serena Ruan <[email protected]>
BenWilson2
left a comment
There was a problem hiding this comment.
Some very minor nits to address, otherwise looks great! Thanks @serena-ruan :)
Signed-off-by: Serena Ruan <[email protected]>
Signed-off-by: Serena Ruan <[email protected]>
Signed-off-by: Serena Ruan <[email protected]>
| params_schema = model_metadata.get_params_schema() | ||
| return _enforce_params_schema(params, params_schema) | ||
| else: | ||
| if hasattr(model_metadata, "get_params_schema"): |
There was a problem hiding this comment.
@harupy I fixed this to make sure if user doesn't pass params the default values are still applied, and added two tests in test_pyfunc_schema_enforcement.py, could you pls review again? Thanks 😄
| else: | ||
| if params: |
There was a problem hiding this comment.
| else: | |
| if params: | |
| if params: |
I think we can remove this else.
| def test_enforce_params_schema_errors_with_python_model(): | ||
| class MyModel(mlflow.pyfunc.PythonModel): | ||
| def predict(self, ctx, model_input, params=None): | ||
| return list(params.values()) if isinstance(params, dict) else None | ||
|
|
||
| params = {"str_param": "string", "int_array": [1, 2, 3], "123": 123} | ||
| signature = infer_signature(["input"]) | ||
| signature_with_params = infer_signature(["input"], params=params) | ||
|
|
||
| with mlflow.start_run(): | ||
| model_info = mlflow.pyfunc.log_model( | ||
| python_model=MyModel(), artifact_path="model1", signature=signature | ||
| ) | ||
| model_info_with_params = mlflow.pyfunc.log_model( | ||
| python_model=MyModel(), artifact_path="model2", signature=signature_with_params | ||
| ) | ||
|
|
||
| loaded_model = mlflow.pyfunc.load_model(model_info.model_uri) | ||
|
|
||
| with pytest.raises( | ||
| MlflowException, | ||
| match=r"`params` can only be specified at inference time if the model signature", | ||
| ): | ||
| loaded_model.predict(["input"], params=params) | ||
|
|
||
| loaded_model_with_params = mlflow.pyfunc.load_model(model_info_with_params.model_uri) | ||
| with pytest.raises(MlflowException, match=r"Parameters must be a dictionary. Got type 'list'"): | ||
| loaded_model_with_params.predict(["input"], params=[1, 2, 3]) | ||
|
|
||
| with mock.patch("mlflow.models.utils._logger.warning") as mock_warning: | ||
| loaded_model_with_params.predict(["input"], params={123: 456}) | ||
| mock_warning.assert_called_with( | ||
| "Keys in parameters should be of type `str`, but received non-string keys." | ||
| "Converting all keys to string..." | ||
| ) |
There was a problem hiding this comment.
@harupy Sure, here are the two tests split:
Test 1
def test_enforce_params_schema_errors_with_model_without_params():
class MyModel(mlflow.pyfunc.PythonModel):
def predict(self, ctx, model_input, params=None):
return list(params.values()) if isinstance(params, dict) else None
params = {"str_param": "string", "int_array": [1, 2, 3], "123": 123}
signature = infer_signature(["input"])
with mlflow.start_run():
model_info = mlflow.pyfunc.log_model(
python_model=MyModel(), artifact_path="model1", signature=signature
)
loaded_model = mlflow.pyfunc.load_model(model_info.model_uri)
with pytest.raises(
MlflowException,
match=r"`params` can only be specified at inference time if the model signature",
):
loaded_model.predict(["input"], params=params)Test 2
def test_enforce_params_schema_errors_with_model_with_params():
class MyModel(mlflow.pyfunc.PythonModel):
def predict(self, ctx, model_input, params=None):
return list(params.values()) if isinstance(params, dict) else None
params = {"str_param": "string", "int_array": [1, 2, 3], "123": 123}
signature_with_params = infer_signature(["input"], params=params)
with mlflow.start_run():
model_info_with_params = mlflow.pyfunc.log_model(
python_model=MyModel(), artifact_path="model2", signature=signature_with_params
)
loaded_model_with_params = mlflow.pyfunc.load_model(model_info_with_params.model_uri)
with pytest.raises(MlflowException, match=r"Parameters must be a dictionary. Got type 'list'"):
loaded_model_with_params.predict(["input"], params=[1, 2, 3])
with mock.patch("mlflow.models.utils._logger.warning") as mock_warning:
loaded_model_with_params.predict(["input"], params={123: 456})
mock_warning.assert_called_with(
"Keys in parameters should be of type `str`, but received non-string keys."
"Converting all keys to string..."
)Click here to see the prompt and usage data
Prompt:
split this test into two
```
def test_enforce_params_schema_errors_with_python_model():
class MyModel(mlflow.pyfunc.PythonModel):
def predict(self, ctx, model_input, params=None):
return list(params.values()) if isinstance(params, dict) else None
params = {"str_param": "string", "int_array": [1, 2, 3], "123": 123}
signature = infer_signature(["input"])
signature_with_params = infer_signature(["input"], params=params)
with mlflow.start_run():
model_info = mlflow.pyfunc.log_model(
python_model=MyModel(), artifact_path="model1", signature=signature
)
model_info_with_params = mlflow.pyfunc.log_model(
python_model=MyModel(), artifact_path="model2", signature=signature_with_params
)
loaded_model = mlflow.pyfunc.load_model(model_info.model_uri)
with pytest.raises(
MlflowException,
match=r"`params` can only be specified at inference time if the model signature",
):
loaded_model.predict(["input"], params=params)
loaded_model_with_params = mlflow.pyfunc.load_model(model_info_with_params.model_uri)
with pytest.raises(MlflowException, match=r"Parameters must be a dictionary. Got type 'list'"):
loaded_model_with_params.predict(["input"], params=[1, 2, 3])
with mock.patch("mlflow.models.utils._logger.warning") as mock_warning:
loaded_model_with_params.predict(["input"], params={123: 456})
mock_warning.assert_called_with(
"Keys in parameters should be of type `str`, but received non-string keys."
"Converting all keys to string..."
)
```
Usage:
{
"prompt_tokens": 420,
"completion_tokens": 474,
"total_tokens": 894,
"estimated_cost_in_usd": 0.04104
}Signed-off-by: Serena Ruan <[email protected]>
Signed-off-by: Serena Ruan <[email protected]>
Related Issues/PRs
Depends on #8976
What changes are proposed in this pull request?
Add docs for inference params and new ModelSignature
How is this patch tested?
Does this PR change the documentation?
Release Notes
Is this a user-facing change?
Add docs for supporting
paramsin model inference.What component(s), interfaces, languages, and integrations does this PR affect?
Components
area/artifacts: Artifact stores and artifact loggingarea/build: Build and test infrastructure for MLflowarea/docs: MLflow documentation pagesarea/examples: Example codearea/model-registry: Model Registry service, APIs, and the fluent client calls for Model Registryarea/models: MLmodel format, model serialization/deserialization, flavorsarea/recipes: Recipes, Recipe APIs, Recipe configs, Recipe Templatesarea/projects: MLproject format, project running backendsarea/scoring: MLflow Model server, model deployment tools, Spark UDFsarea/server-infra: MLflow Tracking server backendarea/tracking: Tracking Service, tracking client APIs, autologgingInterface
area/uiux: Front-end, user experience, plotting, JavaScript, JavaScript dev serverarea/docker: Docker use across MLflow's components, such as MLflow Projects and MLflow Modelsarea/sqlalchemy: Use of SQLAlchemy in the Tracking Service or Model Registryarea/windows: Windows supportLanguage
language/r: R APIs and clientslanguage/java: Java APIs and clientslanguage/new: Proposals for new client languagesIntegrations
integrations/azure: Azure and Azure ML integrationsintegrations/sagemaker: SageMaker integrationsintegrations/databricks: Databricks integrationsHow should the PR be classified in the release notes? Choose one:
rn/breaking-change- The PR will be mentioned in the "Breaking Changes" sectionrn/none- No description will be included. The PR will be mentioned only by the PR number in the "Small Bugfixes and Documentation Updates" sectionrn/feature- A new user-facing feature worth mentioning in the release notesrn/bug-fix- A user-facing bug fix worth mentioning in the release notesrn/documentation- A user-facing documentation change worth mentioning in the release notes