Skip to content

Commit 59e0602

Browse files
authored
[DeepSparse Evaluation API] UX Improvements (#1568)
* initial commit * add some more tests for hardening * Update src/deepsparse/evaluation/cli.py * Update src/deepsparse/transformers/pipelines/text_generation/pipeline.py * Apply suggestions from code review * quality * Update test_evaluator.py * quality
1 parent cb52d6e commit 59e0602

File tree

9 files changed

+132
-150
lines changed

9 files changed

+132
-150
lines changed

src/deepsparse/evaluation/cli.py

+11-19
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
Module for evaluating models on the various evaluation integrations
2121
2222
OPTIONS:
23-
--target TARGET A path to a remote or local directory containing ONNX/torch model
23+
--model_path MODEL_PATH
24+
A path to an ONNX model, local directory containing ONNX model
2425
(including all the auxiliary files) or a SparseZoo stub
2526
-d DATASET, --dataset DATASET
2627
The dataset to evaluate on. The user may pass multiple datasets
@@ -30,9 +31,7 @@
3031
integration name that is registered in the evaluation registry
3132
-e ENGINE_TYPE, --engine_type ENGINE_TYPE
3233
Inference engine to use for the evaluation. The default
33-
is the DeepSparse engine. If the evaluation should be run
34-
without initializing a pipeline (e.g. for the evaluation
35-
of a torch model), the engine type should be set to None
34+
is the DeepSparse engine.
3635
-s SAVE_PATH, --save_path SAVE_PATH
3736
The path to save the evaluation results.
3837
By default the results will be saved in the
@@ -90,10 +89,10 @@
9089
)
9190
)
9291
@click.option(
93-
"--target",
92+
"--model_path",
9493
type=click.Path(dir_okay=True, file_okay=True),
9594
required=True,
96-
help="A path to a remote or local directory containing ONNX/torch model "
95+
help="A path to an ONNX model, local directory containing ONNX model"
9796
"(including all the auxiliary files) or a SparseZoo stub",
9897
)
9998
@click.option(
@@ -118,9 +117,7 @@
118117
type=click.Choice([DEEPSPARSE_ENGINE, ORT_ENGINE, TORCHSCRIPT_ENGINE]),
119118
default=DEEPSPARSE_ENGINE,
120119
help="The engine to use for the evaluation. The default is the "
121-
"DeepSparse engine. If the evaluation should be run without "
122-
"initializing a pipeline (e.g. for the evaluation of a torch "
123-
"model), the engine type should be set to None",
120+
"DeepSparse engine. ",
124121
)
125122
@click.option(
126123
"-s",
@@ -167,7 +164,7 @@
167164
)
168165
@click.argument("integration_args", nargs=-1, type=click.UNPROCESSED)
169166
def main(
170-
target,
167+
model_path,
171168
dataset,
172169
integration,
173170
engine_type,
@@ -183,14 +180,9 @@ def main(
183180
# format kwargs to a dict
184181
integration_args = args_to_dict(integration_args)
185182

186-
_LOGGER.info(f"Target to evaluate: {target}")
187-
if engine_type:
188-
_LOGGER.info(f"A pipeline with the engine type: {engine_type} will be created")
189-
else:
190-
_LOGGER.info(
191-
"No engine type specified. The target "
192-
"will be evaluated using the native framework"
193-
)
183+
_LOGGER.info(
184+
f"Creating {engine_type} pipeline to evaluate from model path: {model_path}"
185+
)
194186

195187
_LOGGER.info(
196188
f"Datasets to evaluate on: {datasets}\n"
@@ -201,7 +193,7 @@ def main(
201193
)
202194

203195
result: Result = evaluate(
204-
target=target,
196+
model=model_path,
205197
datasets=datasets,
206198
integration=integration,
207199
engine_type=engine_type,

src/deepsparse/evaluation/evaluator.py

+22-12
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414
import logging
15-
from typing import Any, List, Optional, Union
15+
from pathlib import Path
16+
from typing import List, Optional, Union
1617

18+
from deepsparse import Pipeline
1719
from deepsparse.evaluation.registry import EvaluationRegistry
1820
from deepsparse.evaluation.results import Result
19-
from deepsparse.evaluation.utils import create_model_from_target
21+
from deepsparse.evaluation.utils import create_pipeline
2022
from deepsparse.operators.engine_operator import (
2123
DEEPSPARSE_ENGINE,
2224
ORT_ENGINE,
@@ -30,30 +32,38 @@
3032

3133

3234
def evaluate(
33-
target: Any,
35+
model: Union[Pipeline, Path, str],
3436
datasets: Union[str, List[str]],
3537
integration: Optional[str] = None,
3638
engine_type: Union[
37-
DEEPSPARSE_ENGINE, ORT_ENGINE, TORCHSCRIPT_ENGINE, None
39+
DEEPSPARSE_ENGINE, ORT_ENGINE, TORCHSCRIPT_ENGINE
3840
] = DEEPSPARSE_ENGINE,
3941
batch_size: int = 1,
4042
splits: Union[List[str], str, None] = None,
4143
metrics: Union[List[str], str, None] = None,
4244
**kwargs,
4345
) -> Result:
4446

45-
# if target is a string, turn it into an appropriate model/pipeline
46-
# otherwise assume it is a model/pipeline
47-
model = (
48-
create_model_from_target(target, engine_type)
49-
if isinstance(target, str)
50-
else target
47+
if isinstance(model, Pipeline):
48+
_LOGGER.info(
49+
"Passed a Pipeline object into evaluate function. This will "
50+
"override the following arguments:"
51+
)
52+
batch_size = model.batch_size
53+
_LOGGER.info(f"batch_size: {batch_size}")
54+
engine_type = engine_type
55+
_LOGGER.info(f"engine_type: {engine_type}")
56+
57+
# if target is a string, turn it into an appropriate pipeline
58+
# otherwise assume it is a pipeline
59+
pipeline = (
60+
create_pipeline(model, engine_type) if isinstance(model, (Path, str)) else model
5161
)
5262

53-
eval_integration = EvaluationRegistry.resolve(model, datasets, integration)
63+
eval_integration = EvaluationRegistry.resolve(pipeline, datasets, integration)
5464

5565
return eval_integration(
56-
model=model,
66+
pipeline=pipeline,
5767
datasets=datasets,
5868
engine_type=engine_type,
5969
batch_size=batch_size,

src/deepsparse/evaluation/registry.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
Implementation of a registry for evaluation functions
1616
"""
1717
import logging
18-
from typing import Any, Callable, List, Optional, Union
18+
from typing import Callable, List, Optional, Union
1919

20+
from deepsparse import Pipeline
2021
from sparsezoo.utils.registry import RegistryMixin
2122

2223

@@ -38,7 +39,7 @@ def load_from_registry(cls, name: str) -> Callable[..., "Result"]: # noqa: F821
3839
@classmethod
3940
def resolve(
4041
cls,
41-
model: Any,
42+
pipeline: Pipeline,
4243
datasets: Union[str, List[str]],
4344
integration: Optional[str] = None,
4445
) -> Callable[..., "Result"]: # noqa: F821
@@ -59,12 +60,12 @@ def resolve(
5960
"No integration specified, inferring the evaluation"
6061
"function from the input arguments..."
6162
)
62-
integration = resolve_integration(model, datasets)
63+
integration = resolve_integration(pipeline, datasets)
6364

6465
if integration is None:
6566
raise ValueError(
6667
"Unable to resolve an evaluation function for the given model. "
67-
"Specify an integration name or use a model that is supported "
68+
"Specify an integration name or use a pipeline that is supported "
6869
)
6970
_LOGGER.info(f"Inferred the evaluation function: {integration}")
7071

src/deepsparse/evaluation/utils.py

+31-54
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,11 @@
1515
import os
1616
from typing import Any, Dict, List, Optional, Tuple, Union
1717

18-
19-
try:
20-
from transformers import AutoModelForCausalLM, PreTrainedModel
21-
22-
transformers_error = None
23-
except ImportError as import_error:
24-
transformers_error = import_error
25-
26-
2718
from deepsparse import Pipeline
28-
from deepsparse.operators.engine_operator import DEEPSPARSE_ENGINE, ORT_ENGINE
2919

3020

3121
__all__ = [
32-
"create_model_from_target",
22+
"create_pipeline",
3323
"get_save_path",
3424
"args_to_dict",
3525
"resolve_integration",
@@ -57,36 +47,36 @@ def potentially_check_dependency_import(integration_name: str) -> bool:
5747

5848

5949
def resolve_integration(
60-
model: Union[Pipeline, "PreTrainedModel"], datasets: Union[str, List[str]]
50+
pipeline: Pipeline, datasets: Union[str, List[str]]
6151
) -> Union[str, None]:
6252
"""
63-
Given a model and dataset, infer the name of the evaluation integration
53+
Given a pipeline and dataset, infer the name of the evaluation integration
6454
to use. If unable to infer a name, return None.
6555
6656
Currently:
6757
if the model is a generative language model,
6858
default to 'lm-evaluation-harness' otherwise return None
6959
70-
:param model: The model to infer the integration for
60+
:param pipeline: The pipeline to infer the integration for
7161
:param datasets: The datasets to infer the integration for
7262
:return: The name of the integration to use or None if unable to infer
7363
"""
74-
if if_generative_language_model(model):
64+
if if_generative_language_model(pipeline):
7565
return LM_EVALUATION_HARNESS
7666
return None
7767

7868

79-
def if_generative_language_model(model: Any) -> bool:
69+
def if_generative_language_model(pipeline: Pipeline) -> bool:
8070
"""
8171
Checks if the model is a generative language model.
8272
"""
83-
_check_transformers_dependency()
84-
if isinstance(model, Pipeline):
85-
return model.__class__.__name__ == "TextGenerationPipeline"
86-
elif isinstance(model, PreTrainedModel):
87-
return "CausalLM" in model.__class__.__name__
88-
else:
89-
return False
73+
pipeline_name = pipeline.__class__.__name__
74+
if pipeline_name == "TextGenerationPipeline" or (
75+
pipeline_name == "TextGenerationPipelineNoKVCache"
76+
):
77+
return True
78+
79+
return False
9080

9181

9282
def args_to_dict(args: Tuple[Any, ...]) -> Dict[str, Any]:
@@ -134,43 +124,30 @@ def get_save_path(
134124
return os.path.join(base_path, file_name)
135125

136126

137-
def create_model_from_target(
138-
target: str,
127+
def create_pipeline(
128+
model_path: str,
139129
engine_type: Optional[str] = None,
140130
**kwargs,
141-
) -> Union[Pipeline, "AutoModelForCausalLM"]:
131+
) -> Pipeline:
142132
"""
143-
Create a model or a pipeline from a target path.
133+
Create a pipeline for evaluation
144134
145-
Note: This function is currently limited to:
146-
- creating pipelines of type 'text-generation'
147-
- creating dense huggingface models of type 'AutoModelForCausalLM'
148-
This function will be expanded in the future to support more
149-
model types and frameworks.
135+
Note: This function is currently primarily
136+
focused on creating pipelines of type 'text-generation'
137+
This function will be expanded in the future to support
138+
more tasks and models
150139
151-
:param target: The target path to initialize the
140+
:param model_path: The target path to initialize the
152141
text generation model from. This can be a local
153142
or remote path to the model or a sparsezoo stub
154143
:param engine_type: The engine type to initialize the model with.
155-
:return: The initialized model
144+
:return: The initialized pipeline
156145
"""
157-
_check_transformers_dependency()
158-
159-
if engine_type in [DEEPSPARSE_ENGINE, ORT_ENGINE]:
160-
return Pipeline.create(
161-
task="text-generation",
162-
model_path=target,
163-
sequence_length=kwargs.pop("sequence_length", 2048),
164-
engine_type=engine_type,
165-
batch_size=kwargs.pop("batch_size", 1),
166-
**kwargs,
167-
)
168-
else:
169-
return AutoModelForCausalLM.from_pretrained(target, **kwargs)
170-
171-
172-
def _check_transformers_dependency():
173-
if transformers_error:
174-
raise ImportError(
175-
"transformers is needed to use this module"
176-
) from transformers_error
146+
return Pipeline.create(
147+
task=kwargs.pop("task", "text-generation"),
148+
model_path=model_path,
149+
sequence_length=kwargs.pop("sequence_length", 2048),
150+
engine_type=engine_type,
151+
batch_size=kwargs.pop("batch_size", 1),
152+
**kwargs,
153+
)

src/deepsparse/transformers/pipelines/text_generation/pipeline.py

+8
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,14 @@ def sequence_length(self) -> int:
357357
"""
358358
return self.ops["single_engine"].sequence_length
359359

360+
@property
361+
def batch_size(self) -> int:
362+
return self.ops["single_engine"].batch_size
363+
364+
@property
365+
def engine_type(self) -> str:
366+
return self.ops["single_engine"]._engine_type
367+
360368
def _get_continuous_batching_scheduler(
361369
self, batch_sizes: List[int], engines: List[EngineOperator]
362370
) -> ContinuousBatchingScheduler:

src/deepsparse/transformers/pipelines/text_generation/pipeline_no_kv_cache.py

+8
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,11 @@ def expand_inputs(self, items, batch_size):
127127
out, orig_batch_size = split_engine_inputs(items, batch_size)
128128
combined_batches = [{"input_ids": b[0], "attention_mask": b[1]} for b in out]
129129
return combined_batches, orig_batch_size
130+
131+
@property
132+
def batch_size(self) -> int:
133+
return self.ops["engine_operator"].batch_size
134+
135+
@property
136+
def engine_type(self) -> str:
137+
return self.ops["engine_operator"]._engine_type

tests/deepsparse/evaluation/integrations/test_lm_evaluation_harness.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,21 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from transformers import AutoModelForCausalLM
16+
1517
import pytest
1618
from deepsparse.evaluation.integrations import try_import_lm_evaluation_harness
17-
from deepsparse.evaluation.utils import create_model_from_target
19+
from deepsparse.evaluation.utils import create_pipeline
1820

1921

2022
@pytest.mark.parametrize(
2123
"pipeline, model_torch",
2224
[
2325
(
24-
create_model_from_target(
26+
create_pipeline(
2527
"hf:mgoin/TinyStories-1M-deepsparse", engine_type="onnxruntime"
2628
),
27-
create_model_from_target("roneneldan/TinyStories-1M"),
29+
AutoModelForCausalLM.from_pretrained("roneneldan/TinyStories-1M"),
2830
)
2931
],
3032
)

0 commit comments

Comments
 (0)