From 6c75b659ca747ae7127e63cbf8df4f236b4a5abe Mon Sep 17 00:00:00 2001 From: Dipika Sikka Date: Tue, 17 Oct 2023 16:20:14 -0400 Subject: [PATCH 01/12] initial functionality and working example with image classification --- .../v2/image_classification/buddy.jpeg | Bin 0 -> 64557 bytes src/deepsparse/v2/pipeline.py | 3 +-- src/deepsparse/v2/schedulers/scheduler.py | 1 + src/deepsparse/v2/schedulers/scheduler_group.py | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 src/deepsparse/v2/image_classification/buddy.jpeg diff --git a/src/deepsparse/v2/image_classification/buddy.jpeg b/src/deepsparse/v2/image_classification/buddy.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..496fc207c4562d18661b92c4b98c6a69accab340 GIT binary patch literal 64557 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

c*~*Ktu8bd;Y(D8o^fa)Ne~Den)++Oji%SZ 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 INl^#?*;%B4n*aa+ literal 0 HcmV?d00001 diff --git a/src/deepsparse/v2/pipeline.py b/src/deepsparse/v2/pipeline.py index e58f8a5191..19ed5f6360 100644 --- a/src/deepsparse/v2/pipeline.py +++ b/src/deepsparse/v2/pipeline.py @@ -18,6 +18,7 @@ from deepsparse.v2.operators import Operator from deepsparse.v2.routers import Router from deepsparse.v2.schedulers import OperatorScheduler, SchedulerGroup +from deepsparse.v2.utils import Context __all__ = ["Pipeline"] @@ -63,8 +64,6 @@ def run(self, *args, **kwargs): :param inp: input to the operator. expected to be of any type that is expected by the operator. - :param context: context to store the current the inputs, outputs, and operator - for each step of the router. """ next_step = self.router.START_ROUTE diff --git a/src/deepsparse/v2/schedulers/scheduler.py b/src/deepsparse/v2/schedulers/scheduler.py index 7d4f249444..a13fbeb040 100644 --- a/src/deepsparse/v2/schedulers/scheduler.py +++ b/src/deepsparse/v2/schedulers/scheduler.py @@ -14,6 +14,7 @@ from concurrent.futures import Future, ThreadPoolExecutor +from typing import Any from deepsparse.v2.operators import Operator diff --git a/src/deepsparse/v2/schedulers/scheduler_group.py b/src/deepsparse/v2/schedulers/scheduler_group.py index 7f00a3c17c..75607504cb 100644 --- a/src/deepsparse/v2/schedulers/scheduler_group.py +++ b/src/deepsparse/v2/schedulers/scheduler_group.py @@ -14,7 +14,7 @@ from concurrent.futures import Future -from typing import List +from typing import Any, List from deepsparse.v2.operators import Operator from deepsparse.v2.schedulers.scheduler import OperatorScheduler From 75de1038d3ffba5074b95f73f5a8ca78e38d57d1 Mon Sep 17 00:00:00 2001 From: Dipika Sikka Date: Tue, 17 Oct 2023 16:25:29 -0400 Subject: [PATCH 02/12] remove testing image --- .../v2/image_classification/buddy.jpeg | Bin 64557 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/deepsparse/v2/image_classification/buddy.jpeg diff --git a/src/deepsparse/v2/image_classification/buddy.jpeg b/src/deepsparse/v2/image_classification/buddy.jpeg deleted file mode 100644 index 496fc207c4562d18661b92c4b98c6a69accab340..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64557 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

c*~*Ktu8bd;Y(D8o^fa)Ne~Den)++Oji%SZ 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 INl^#?*;%B4n*aa+ From aa5d88500b28785137f6266ad77a2334199dba85 Mon Sep 17 00:00:00 2001 From: Dipika Sikka Date: Tue, 31 Oct 2023 17:53:45 -0400 Subject: [PATCH 03/12] rebase fixes --- src/deepsparse/v2/pipeline.py | 1 - src/deepsparse/v2/schedulers/scheduler.py | 1 - src/deepsparse/v2/schedulers/scheduler_group.py | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/deepsparse/v2/pipeline.py b/src/deepsparse/v2/pipeline.py index 19ed5f6360..acf1094e31 100644 --- a/src/deepsparse/v2/pipeline.py +++ b/src/deepsparse/v2/pipeline.py @@ -18,7 +18,6 @@ from deepsparse.v2.operators import Operator from deepsparse.v2.routers import Router from deepsparse.v2.schedulers import OperatorScheduler, SchedulerGroup -from deepsparse.v2.utils import Context __all__ = ["Pipeline"] diff --git a/src/deepsparse/v2/schedulers/scheduler.py b/src/deepsparse/v2/schedulers/scheduler.py index a13fbeb040..7d4f249444 100644 --- a/src/deepsparse/v2/schedulers/scheduler.py +++ b/src/deepsparse/v2/schedulers/scheduler.py @@ -14,7 +14,6 @@ from concurrent.futures import Future, ThreadPoolExecutor -from typing import Any from deepsparse.v2.operators import Operator diff --git a/src/deepsparse/v2/schedulers/scheduler_group.py b/src/deepsparse/v2/schedulers/scheduler_group.py index 75607504cb..7f00a3c17c 100644 --- a/src/deepsparse/v2/schedulers/scheduler_group.py +++ b/src/deepsparse/v2/schedulers/scheduler_group.py @@ -14,7 +14,7 @@ from concurrent.futures import Future -from typing import Any, List +from typing import List from deepsparse.v2.operators import Operator from deepsparse.v2.schedulers.scheduler import OperatorScheduler From 8cc63eeb44869c533ed6594e8f4f8366ee5554e5 Mon Sep 17 00:00:00 2001 From: Dipika Sikka Date: Tue, 17 Oct 2023 16:20:14 -0400 Subject: [PATCH 04/12] initial functionality and working example with image classification --- .../v2/image_classification/buddy.jpeg | Bin 0 -> 64557 bytes src/deepsparse/v2/pipeline.py | 3 ++- src/deepsparse/v2/schedulers/scheduler.py | 1 + src/deepsparse/v2/schedulers/scheduler_group.py | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 src/deepsparse/v2/image_classification/buddy.jpeg diff --git a/src/deepsparse/v2/image_classification/buddy.jpeg b/src/deepsparse/v2/image_classification/buddy.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..496fc207c4562d18661b92c4b98c6a69accab340 GIT binary patch literal 64557 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

c*~*Ktu8bd;Y(D8o^fa)Ne~Den)++Oji%SZ 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 INl^#?*;%B4n*aa+ literal 0 HcmV?d00001 diff --git a/src/deepsparse/v2/pipeline.py b/src/deepsparse/v2/pipeline.py index acf1094e31..7a37d7d4f6 100644 --- a/src/deepsparse/v2/pipeline.py +++ b/src/deepsparse/v2/pipeline.py @@ -18,6 +18,7 @@ from deepsparse.v2.operators import Operator from deepsparse.v2.routers import Router from deepsparse.v2.schedulers import OperatorScheduler, SchedulerGroup +from deepsparse.v2.utils import Context __all__ = ["Pipeline"] @@ -39,7 +40,7 @@ class Pipeline(Operator): :param schedulers: A list of schedulers to run operators. """ - + def __init__( self, ops: Union[Dict[str, Operator], List[Operator]], diff --git a/src/deepsparse/v2/schedulers/scheduler.py b/src/deepsparse/v2/schedulers/scheduler.py index 7d4f249444..a13fbeb040 100644 --- a/src/deepsparse/v2/schedulers/scheduler.py +++ b/src/deepsparse/v2/schedulers/scheduler.py @@ -14,6 +14,7 @@ from concurrent.futures import Future, ThreadPoolExecutor +from typing import Any from deepsparse.v2.operators import Operator diff --git a/src/deepsparse/v2/schedulers/scheduler_group.py b/src/deepsparse/v2/schedulers/scheduler_group.py index 7f00a3c17c..75607504cb 100644 --- a/src/deepsparse/v2/schedulers/scheduler_group.py +++ b/src/deepsparse/v2/schedulers/scheduler_group.py @@ -14,7 +14,7 @@ from concurrent.futures import Future -from typing import List +from typing import Any, List from deepsparse.v2.operators import Operator from deepsparse.v2.schedulers.scheduler import OperatorScheduler From ab2b7112301a4ff3b2a5fcf0ac0b868297906d3c Mon Sep 17 00:00:00 2001 From: Dipika Sikka Date: Fri, 20 Oct 2023 10:43:17 -0400 Subject: [PATCH 05/12] text gen --- .../v2/operators/engine_operator.py | 3 +- src/deepsparse/v2/text_generation/__init__.py | 0 .../autoregressive_preprocess_operator.py | 60 +++++++ .../multi_engine_prefill_operator.py | 15 ++ .../v2/text_generation/nl_engine_operator.py | 154 ++++++++++++++++++ src/deepsparse/v2/text_generation/pipeline.py | 47 ++++++ .../prefill_preprocess_operator.py | 21 +++ 7 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 src/deepsparse/v2/text_generation/__init__.py create mode 100644 src/deepsparse/v2/text_generation/autoregressive_preprocess_operator.py create mode 100644 src/deepsparse/v2/text_generation/multi_engine_prefill_operator.py create mode 100644 src/deepsparse/v2/text_generation/nl_engine_operator.py create mode 100644 src/deepsparse/v2/text_generation/pipeline.py create mode 100644 src/deepsparse/v2/text_generation/prefill_preprocess_operator.py diff --git a/src/deepsparse/v2/operators/engine_operator.py b/src/deepsparse/v2/operators/engine_operator.py index 2c61755df9..8cdb9dcdf7 100644 --- a/src/deepsparse/v2/operators/engine_operator.py +++ b/src/deepsparse/v2/operators/engine_operator.py @@ -60,6 +60,7 @@ def __init__( scheduler: Scheduler = None, input_shapes: List[List[int]] = None, engine_context: Optional[Context] = None, + engine_kwargs: Dict = None, ): self._batch_size = batch_size @@ -87,7 +88,7 @@ def __init__( self._engine_args = engine_args self._engine_type = engine_type - self.engine = self.create_engine() + self.engine = self.create_engine(**engine_kwargs) @property def batch_size(self) -> int: diff --git a/src/deepsparse/v2/text_generation/__init__.py b/src/deepsparse/v2/text_generation/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/deepsparse/v2/text_generation/autoregressive_preprocess_operator.py b/src/deepsparse/v2/text_generation/autoregressive_preprocess_operator.py new file mode 100644 index 0000000000..96a4f7f87a --- /dev/null +++ b/src/deepsparse/v2/text_generation/autoregressive_preprocess_operator.py @@ -0,0 +1,60 @@ +from deepsparse.v2.operators import Operator +from deepsparse.transformers.utils.helpers import create_causal_mask +from pydantic import BaseModel +import numpy + +__all__ = ["AutoRegressiveOperator"] + +class AutoRegressiveInput(BaseModel): + tokens: Any = Field(description="tokens") + kv_cache: DecoderKVCache = Field(description="kv_cache object") + +class AutoRegressiveOutput(BaseModel): + engine_inputs: list = Field(description="engine inputs maps") + +class AutoRegressiveOperator(Operator): + input_schema = AutoRegressiveInput + output_schema = AutoRegressiveOutput + + def __init__(self, sequence_length: int): + self.sequence_length = sequence_length + + def run(inp: Any, context: Optional[Context]): + kv_cache = inp.kv_cache + tokens = inp.tokens + engine_input_names = inp.engine_input_names ## property of the engine + + num_total_processed_tokens = kv_cache.total_num_processed_tokens + new_token = tokens[-1] + # padding is added to left, so attention mask is 1s from the + # right up to the number of total tokens (prompt + generated) + attention_mask = numpy.zeros((1, self.sequence_length), dtype=numpy.int64) + num_attention_entries_to_unmask = min( + num_total_processed_tokens + 1, self.sequence_length + ) # cap by seq len + attention_mask[:, -num_attention_entries_to_unmask:] = 1 + positions = numpy.array([[num_total_processed_tokens]], dtype=numpy.int64) + input_ids = numpy.array([[new_token]]) + causal_mask = create_causal_mask(input_ids, attention_mask) + + # filter out the inputs that are not needed by the engine + engine_inputs_map = dict( + input_ids=input_ids, + attention_mask=attention_mask, + causal_mask=causal_mask, + positions=positions, + ) + + engine_inputs = [ + engine_inputs_map[name] for name in engine_input_names + ] + + return {"engine_inputs": engine_inputs} + + + """ next operator to call engine + generated_logits = self.engine(engine_inputs, kv_cache) + + return generated_logits + """ + \ No newline at end of file diff --git a/src/deepsparse/v2/text_generation/multi_engine_prefill_operator.py b/src/deepsparse/v2/text_generation/multi_engine_prefill_operator.py new file mode 100644 index 0000000000..7f19c0f25f --- /dev/null +++ b/src/deepsparse/v2/text_generation/multi_engine_prefill_operator.py @@ -0,0 +1,15 @@ +from deepsparse.v2.operators import Operator + + +class MultiEnginePrefill(Operator): + def __init__(self, prompt_sequence_length): + self.prompt_sequence_length = prompt_sequence_length + self.sequence_length = sequence_length + self.onnx_input_names_no_cache = onnx_input_names_no_cache + + def run(self, inp: Any, context: Optional[Context]): + ## process token_batch + ## return to run through multi_engie; store prompt logits + tokens processed (pipeline variable?) + ## who is looping through the token batches and keeping track? + + diff --git a/src/deepsparse/v2/text_generation/nl_engine_operator.py b/src/deepsparse/v2/text_generation/nl_engine_operator.py new file mode 100644 index 0000000000..b4ad941882 --- /dev/null +++ b/src/deepsparse/v2/text_generation/nl_engine_operator.py @@ -0,0 +1,154 @@ +from deepsparse.v2.operators import EngineOperator, Operator +from deepsparse import Context as EngineContext +from typing import Any +from deepsparse.v2.utils import Context +from pydantic import BaseModel, Field +from deepsparse.utils.onnx import CACHE_INPUT_PREFIX, + +__all__ = ["NLEngineOperator"] + +class NlEngineInput(BaseModel): + inputs: Any = Field(description="engine inputs") + kv_cache: DecoderKVCache = Field(description="kv_cache object") + + +class NLEngineOperator(EngineOperator): + input_schema = NlEngineInput + output_schema = None + + def __init__(self, + sequence_length: int, + input_ids_length: int, + enable_multitoken_prefill: bool, + internal_kv_cache: bool = False, + **kwargs): + + ( + onnx_file_path, + output_indices_to_be_cached, + kv_cache_data_type, + ) = overwrite_onnx_model_inputs_for_kv_cache_models( + onnx_file_path=kwargs.get("model_path"), + batch_size=kwargs.get("batch_size", 1), + sequence_length=sequence_length, + input_ids_length=input_ids_length, + ) + + self._can_operate = enable_multitoken_prefill + self.kv_cache_data_type = None + if any(output_indices_to_be_cached): + self.kv_cache_data_type = kv_cache_data_type + if internal_kv_cache and kwargs.get("engine_type") == DEEPSPARSE_ENGINE: + # inform the engine, that are using the kv cache + engine_kwargs = kwargs.get("engine_kwargs") + if not engine_kwargs: + engine_kwargs = {} + engine_kwargs["cached_outputs"] = output_indices_to_be_cached + + kwargs["engine_kwargs"] = engine_kwargs + kwargs["model_path"] = onnx_file_path + super().__init__(**kwargs) + + self.sequence_length = sequence_length + self.input_ids_length = input_ids_length + + @property + def can_operate(self): + return self._can_operate + + def run(self, inp: NlEngineInput, context: Optional[Context]) -> Any: + engine_input = inp.inputs + kv_cache = inp.kv_cache + + inputs = self._add_kv_cache_to_input(engine_input, kv_cache) + if bool(kv_cache.engine_internal_cache): + # conventionally, before dispatching + # inputs to the engine, we validate them + # if val_inp=True. However, in this case + # we want to pass the empty kv cache inputs + # (batch_size=0) to the engine. Therefore, + # we skip the validation + out = self.engine._eng_net.execute_list_out( + inputs, kv_cache.engine_internal_cache + ) + else: + # run the engine without the LIB.kv_cache object + out = super().__call__(inputs) + + logits, *kv_cache_state = out + self._update_kv_cache( + kv_cache_state=kv_cache_state, + input_ids_len=self.input_ids_length, + kv_cache=kv_cache, + ) + return logits + + def _add_kv_cache_to_input(self, engine_input, kv_cache): + kv_cache_state = copy.copy(kv_cache.cached_inputs) + + for idx, input_name in enumerate(self.onnx_input_names_no_cache): + kv_cache_state[input_name] = inp[idx] + + new_inp = [kv_cache_state[name] for name in self.engine.input_names] + return new_inp + + def _update_kv_cache(self, kv_cache_state, input_ids_len, kv_cache): + if bool(kv_cache.engine_internal_cache): + kv_cache.total_num_processed_tokens += input_ids_len + return + + kv_cache_state = { + name: array + for name, array in zip(self.onnx_input_names_cached, kv_cache_state) + } + + kv_cache.update( + state=kv_cache_state, + input_ids_len=input_ids_len, + ) + + @property + def onnx_input_names_no_cache(self) -> List[str]: + """ + :return: The input names for the onnx model, excluding + the potential kv cache inputs + """ + return [ + name + for name in self.engine.input_names + if not name.startswith(CACHE_INPUT_PREFIX) + ] + + @property + def onnx_input_names_cached(self) -> List[str]: + """ + :return: The cached input names for the onnx model + """ + return [ + name + for name in self.engine.input_names + if name.startswith(CACHE_INPUT_PREFIX) + ] + + @property + def cache_shape(self) -> Tuple[int, int, int, int]: + """ + :return: The shape of the kv cache inputs + for the onnx model. The shape is + (batch_size, num_heads, sequence_length, hidden_size) + """ + cache_engine_input_index = next( + i + for i, name in enumerate(self.engine.input_names) + if CACHE_INPUT_PREFIX in name + ) + return self.engine.input_shapes[cache_engine_input_index] + + @property + def output_names(self) -> List[str]: + """ + :return: The output names for the onnx model + """ + return self.engine.output_names + + \ No newline at end of file diff --git a/src/deepsparse/v2/text_generation/pipeline.py b/src/deepsparse/v2/text_generation/pipeline.py new file mode 100644 index 0000000000..4c9cbf7090 --- /dev/null +++ b/src/deepsparse/v2/text_generation/pipeline.py @@ -0,0 +1,47 @@ +from deepsparse.v2 import pipeline + +class TextGenerationPipeline(Pipeline): + def __init__(self, prompt_sequence_length, enable_multitoken_prefill): + transformers_preprocess = TransformersPreprocess() ## set-up config/tokenizer + + single_engine_operator = NLEngineOperator() # set-up engine + multi_engine_operator = NLEngineOperator(enable_multitoken_prefill) # set-up engine + + input_preprocess = Preprocess() # take in config/produce input_tokens, update context variable + tokens_to_engine_input = TokenToEngineInput() ## convert input_tokens to engine_input (depends on if multi-token available) + + + prefill_preprocess = PrefillPreprocess() + ## schema with engine specific values? or the engine itself? + # class variables? + # get tokens based on attn mask, set-up kv_cache, return both + + ## Update the schema as part of the run function in the pipeline? + ## that would put the ownness on the pipeline to be able to check which engine can run/is valid + + + + + ops = {"input_preprocess": input_preprocess, + "prefill_preprovess": prefill_preprocess, + "singe_engine_operator": single_engine_operator, + "multi_engine_operator": multi_engine_operator} + + routes = {"preprocess": {"input_preprocess", "token_to_engine_input"}} + + + + + + + + + + + + + + + + + diff --git a/src/deepsparse/v2/text_generation/prefill_preprocess_operator.py b/src/deepsparse/v2/text_generation/prefill_preprocess_operator.py new file mode 100644 index 0000000000..1a6636f581 --- /dev/null +++ b/src/deepsparse/v2/text_generation/prefill_preprocess_operator.py @@ -0,0 +1,21 @@ +from pydantic import BaseModel +from deepsparse.v2.operators import operator + +class PrefillPreprocessInput(BaseModel): + engine_inputs: list = Field(description="engine inputs") + +class PrefillPreprocessOutput(BaseModel): + tokens: Any = Field(description="tokens") + kv_cache: DecoderKVCache = Field(description="kv_cache object") + +class PrefillPreprocess(Operator): + input_schema = None + output_schema = None + + def run(self, inp: Any, context: Optional[Context]): + engine_inputs = inp.engine_inputs + + tokens = engine_inputs[0][engine_inputs[1].nonzero()].tolist() + kv_cache = get_kv_cache_decoder(...) # requires engine attributes, engine as input? + + return {"tokens": tokens, "kv_cache": kv_cache} From 00cb85e97384506d0d5d196e8077c19bb34d4dbd Mon Sep 17 00:00:00 2001 From: Dipika Sikka Date: Mon, 23 Oct 2023 20:50:43 -0400 Subject: [PATCH 06/12] updates func --- src/deepsparse/v2/operators/__init__.py | 1 - .../v2/operators/engine_operator.py | 7 +- src/deepsparse/v2/operators/operator.py | 8 + src/deepsparse/v2/pipeline.py | 10 +- src/deepsparse/v2/routers/router.py | 37 +++ src/deepsparse/v2/text_generation/__init__.py | 30 ++ .../autoregressive_preprocess_operator.py | 100 +++++-- .../v2/text_generation/compile_logits.py | 45 +++ .../v2/text_generation/kv_cache_operator.py | 77 ++++++ .../multi_engine_prefill_operator.py | 129 ++++++++- .../v2/text_generation/nl_engine_operator.py | 28 +- src/deepsparse/v2/text_generation/pipeline.py | 258 +++++++++++++++--- .../v2/text_generation/prep_for_prefill.py | 54 ++++ .../text_generation/prep_for_single_engine.py | 65 +++++ .../v2/text_generation/process_inputs.py | 121 ++++++++ src/deepsparse/v2/utils/state.py | 52 ++++ 16 files changed, 928 insertions(+), 94 deletions(-) create mode 100644 src/deepsparse/v2/text_generation/compile_logits.py create mode 100644 src/deepsparse/v2/text_generation/kv_cache_operator.py create mode 100644 src/deepsparse/v2/text_generation/prep_for_prefill.py create mode 100644 src/deepsparse/v2/text_generation/prep_for_single_engine.py create mode 100644 src/deepsparse/v2/text_generation/process_inputs.py create mode 100644 src/deepsparse/v2/utils/state.py diff --git a/src/deepsparse/v2/operators/__init__.py b/src/deepsparse/v2/operators/__init__.py index 8f7e6a169d..9d1a9812ac 100644 --- a/src/deepsparse/v2/operators/__init__.py +++ b/src/deepsparse/v2/operators/__init__.py @@ -13,5 +13,4 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - from .operator import * diff --git a/src/deepsparse/v2/operators/engine_operator.py b/src/deepsparse/v2/operators/engine_operator.py index 8cdb9dcdf7..cbc65009ba 100644 --- a/src/deepsparse/v2/operators/engine_operator.py +++ b/src/deepsparse/v2/operators/engine_operator.py @@ -17,7 +17,8 @@ from pydantic import BaseModel, Field -from deepsparse import Context, Engine, MultiModelEngine, Scheduler +from deepsparse import Context as EngineContext +from deepsparse import Engine, MultiModelEngine, Scheduler from deepsparse.benchmark import ORTEngine from deepsparse.utils import join_engine_outputs, model_to_path, split_engine_inputs from deepsparse.v2.operators import Operator @@ -54,7 +55,6 @@ def __init__( self, model_path: str, engine_type: str = DEEPSPARSE_ENGINE, - batch_size: Optional[int] = 1, num_cores: int = None, num_streams: int = None, scheduler: Scheduler = None, @@ -62,9 +62,8 @@ def __init__( engine_context: Optional[Context] = None, engine_kwargs: Dict = None, ): - - self._batch_size = batch_size self.model_path = model_to_path(model_path) + self._batch_size = 1 self.engine_context = engine_context if self.engine_context is not None: diff --git a/src/deepsparse/v2/operators/operator.py b/src/deepsparse/v2/operators/operator.py index c3a3e28b78..42ff42911e 100644 --- a/src/deepsparse/v2/operators/operator.py +++ b/src/deepsparse/v2/operators/operator.py @@ -99,6 +99,14 @@ def run(self, *args, **kwargs) -> Any: """ raise NotImplementedError + def can_operate( + self, inp: Any, context: Context, inference_state: InferenceState + ) -> bool: + """ + Whether or not the given operator can run, based on input and state + """ + raise NotImplementedError + def expand_inputs(self, **kwargs): """ Generic function to handle expanding values. diff --git a/src/deepsparse/v2/pipeline.py b/src/deepsparse/v2/pipeline.py index 7a37d7d4f6..d2a7acd719 100644 --- a/src/deepsparse/v2/pipeline.py +++ b/src/deepsparse/v2/pipeline.py @@ -86,8 +86,14 @@ def run(self, *args, **kwargs): ) # wait for future to resolve - operator_output = output_future.result() - next_step = self.router.next(next_step, self.ops) + operator_output, state_update = output_future.result() + inference_state.update_state(state_update) + + next_step = self.router.next( + next_step, self.ops, operator_output, inference_state + ) + inp = operator_output + return operator_output def __call__(self, *args, **kwargs): diff --git a/src/deepsparse/v2/routers/router.py b/src/deepsparse/v2/routers/router.py index 6050803b5e..fe394868e2 100644 --- a/src/deepsparse/v2/routers/router.py +++ b/src/deepsparse/v2/routers/router.py @@ -38,6 +38,8 @@ class Router: def __init__(self, end_route: Union[str, int], start_route: Union[str, int]): self.START_ROUTE = start_route self.END_ROUTE = end_route + self.route = route + self.SPLIT_ROUTE = None @abstractmethod def next( @@ -105,3 +107,38 @@ def validate(operators: List[Operator]) -> bool: ) return False return True + + +class TextGenerationRouter(Router): + """ + Router for a DAG. Expects graphs be presented in the form of a dictionary, where + keys are the nodes of the graph and the values are the connected nodes. For + nodes with multiple ouput edges, all the nodes will be visited and the first node + where `can_operate` returns True will run. + """ + + def __init__(self, end_route: str, start_route: str, route: Dict): + super().__init__(end_route=end_route, start_route=start_route, route=route) + + def next( + self, + past: str, + ops: Dict[str, Operator], + inp: Any, + inference_state: InferenceState, + ) -> int: + node = past + if isinstance(self.route[node], str): + print(past, self.route[node]) + return self.route[node] + else: + for neighbour_node in self.route[node]: + neighbour_node_op = ops[neighbour_node] + print(node, neighbour_node) + if neighbour_node_op.can_operate(inp, inference_state): + return neighbour_node + raise ValueError("Cannot operate on any of the nodes") + + @staticmethod + def validate(ops) -> bool: + pass diff --git a/src/deepsparse/v2/text_generation/__init__.py b/src/deepsparse/v2/text_generation/__init__.py index e69de29bb2..7d25447729 100644 --- a/src/deepsparse/v2/text_generation/__init__.py +++ b/src/deepsparse/v2/text_generation/__init__.py @@ -0,0 +1,30 @@ +<<<<<<< HEAD +======= +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .autoregressive_preprocess_operator import * +from .compile_logits import * +from .kv_cache_operator import * +from .multi_engine_prefill_operator import * +from .nl_engine_operator import * +from .prep_for_prefill import * +from .prep_for_single_engine import * +from .prepare_for_multi_engine import * +from .process_inputs import * +from .tokens_to_engine_inputs import * + + +from .pipeline import * # isort:skip +>>>>>>> updates func diff --git a/src/deepsparse/v2/text_generation/autoregressive_preprocess_operator.py b/src/deepsparse/v2/text_generation/autoregressive_preprocess_operator.py index 96a4f7f87a..b6db85379e 100644 --- a/src/deepsparse/v2/text_generation/autoregressive_preprocess_operator.py +++ b/src/deepsparse/v2/text_generation/autoregressive_preprocess_operator.py @@ -1,31 +1,79 @@ -from deepsparse.v2.operators import Operator -from deepsparse.transformers.utils.helpers import create_causal_mask -from pydantic import BaseModel +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Any, Optional + import numpy -__all__ = ["AutoRegressiveOperator"] +from deepsparse.transformers.utils.helpers import create_causal_mask +from deepsparse.v2.operators import Operator +from deepsparse.v2.utils import Context, InferenceState, PipelineState -class AutoRegressiveInput(BaseModel): - tokens: Any = Field(description="tokens") - kv_cache: DecoderKVCache = Field(description="kv_cache object") -class AutoRegressiveOutput(BaseModel): - engine_inputs: list = Field(description="engine inputs maps") +__all__ = ["AutoRegressiveOperator"] -class AutoRegressiveOperator(Operator): - input_schema = AutoRegressiveInput - output_schema = AutoRegressiveOutput +class AutoRegressiveOperator(Operator): def __init__(self, sequence_length: int): self.sequence_length = sequence_length - def run(inp: Any, context: Optional[Context]): - kv_cache = inp.kv_cache - tokens = inp.tokens - engine_input_names = inp.engine_input_names ## property of the engine + def can_operate(self, inp: Any, context: Context, inference_state: InferenceState): + if len(inp.tokens) > self.prompt_sequence_length: + return False + + start_token = inference_state.current_state.get("start_token") + end_token = inference_state.current_state.get("end_token") + + if end_token - start_token == 1 and inference_state.current_state.get( + "batches_processed" + ) < inference_state.current_state.get("num_batches"): + return True + return False + + def _fetch_state_update(self, current: dict): + return { + "start_token": current.get("end_token"), + "end_token": current.get("end_token") + 1, + "batches_processed": current.get("batches_processed") + 1, + "num_tokens_processed": current.get("num_tokens_processed") + 1, + } + + def run( + self, + inp: Any, + context: Optional[Context], + inference_state: InferenceState, + pipeline_state: PipelineState, + ): + kv_cache = inp.get("kv_cache") + tokens = inp.get("tokens") + + start_token = inference_state.current_state.get("start_token") + end_token = inference_state.current_state.get("end_token") + engine_input_names = pipeline_state.current_state.get( + "onnx_input_names_no_cache" + ) + + new_token = tokens[start_token:end_token] + num_total_processed_tokens = ( + kv_cache.total_num_processed_tokens + ) # should be same as the state value? + print( + num_total_processed_tokens, + inference_state.current_state.get("num_total_processed_tokens"), + ) - num_total_processed_tokens = kv_cache.total_num_processed_tokens - new_token = tokens[-1] # padding is added to left, so attention mask is 1s from the # right up to the number of total tokens (prompt + generated) attention_mask = numpy.zeros((1, self.sequence_length), dtype=numpy.int64) @@ -49,12 +97,12 @@ def run(inp: Any, context: Optional[Context]): engine_inputs_map[name] for name in engine_input_names ] - return {"engine_inputs": engine_inputs} - - - """ next operator to call engine - generated_logits = self.engine(engine_inputs, kv_cache) + # covered by tokens_to_engine_names --> why does this have to be a dictionary? can also remove tokens_to_engins? + engine_inputs = [engine_inputs_map[name] for name in engine_input_names] - return generated_logits - """ - \ No newline at end of file + state_update = self._fetch_state_update(inference_state.current_state) + return { + "engine_inputs": engine_inputs, + "kv_cache": kv_cache, + "tokens": tokens, + }, state_update diff --git a/src/deepsparse/v2/text_generation/compile_logits.py b/src/deepsparse/v2/text_generation/compile_logits.py new file mode 100644 index 0000000000..5c9f12af19 --- /dev/null +++ b/src/deepsparse/v2/text_generation/compile_logits.py @@ -0,0 +1,45 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +from typing import Any, Optional + +from pydantic import BaseModel, Field + +from deepsparse.v2.operators import Operator +from deepsparse.v2.utils import Context, InferenceState, PipelineState + + +__all__ = ["CompilePromptLogits"] + + +class CompilePromptLogits(Operator): + def run( + self, + inp: Any, + context: Optional[Context], + pipeline_state: PipelineState, + inference_state: InferenceState, + ): + logit_type = "prompt_logits" + logits = inp.get("logits") + + if inference_state.current_state.get(logit_type) is not None: + current_logits = inference_state.current_state.get(logit_type).copy() + current_logits.extend(logits) + else: + current_logits = list(logits) + + state_update = {logit_type: current_logits} + return inp, state_update diff --git a/src/deepsparse/v2/text_generation/kv_cache_operator.py b/src/deepsparse/v2/text_generation/kv_cache_operator.py new file mode 100644 index 0000000000..182b7e62cd --- /dev/null +++ b/src/deepsparse/v2/text_generation/kv_cache_operator.py @@ -0,0 +1,77 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Any, Optional + +from pydantic import BaseModel, Field + +from deepsparse.transformers.utils import DecoderKVCache +from deepsparse.transformers.utils.helpers import ( + initialize_kv_cache_state, + prepends_bos_token, +) +from deepsparse.v2.operators import Operator +from deepsparse.v2.utils import Context, InferenceState, PipelineState + + +__all__ = ["KVCacheCreator"] + + +class KVCacheCreatorOutput(BaseModel): + kv_cache: Any = Field(description="KV Cache Created") # DecoderKVCache + + +class KVCacheCreatorInput(BaseModel): + cache_shape: Any = Field(description="shape") + kv_cache_data_type: Any = Field(description="data type") + output_names: Any = Field(description="output names") + + +class KVCacheCreator(Operator): + input_schema = KVCacheCreatorInput + output_schema = KVCacheCreatorOutput + + def __init__( + self, tokenizer, sequence_length, prompt_sequence_length, internal_kv_cache + ): + self.tokenizer = tokenizer + self.prompt_sequence_length = prompt_sequence_length + self.internal_kv_cache = internal_kv_cache + self.sequence_length = sequence_length + + def run( + self, + inp: Any, + context: Optional[Context], + pipeline_state: PipelineState, + inference_state: InferenceState, + ): + cache_shape = inp.cache_shape + kv_cache_data_type = inp.kv_cache_data_type + output_names = inp.output_names + + kv_cache_state = initialize_kv_cache_state( + cache_shape=cache_shape, + kv_cache_data_type=kv_cache_data_type, + output_names=output_names, + length=self.sequence_length - self.prompt_sequence_length, + empty=bool(self.internal_kv_cache), + ) + + kv_cache = DecoderKVCache(self.internal_kv_cache) + kv_cache.setup( + state=kv_cache_state, + freeze_first_position=prepends_bos_token(self.tokenizer), + ) + return {"kv_cache": kv_cache}, {} diff --git a/src/deepsparse/v2/text_generation/multi_engine_prefill_operator.py b/src/deepsparse/v2/text_generation/multi_engine_prefill_operator.py index 7f19c0f25f..dafc282bde 100644 --- a/src/deepsparse/v2/text_generation/multi_engine_prefill_operator.py +++ b/src/deepsparse/v2/text_generation/multi_engine_prefill_operator.py @@ -1,15 +1,132 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from enum import Enum +from typing import Any, Optional + +import numpy + +from deepsparse.transformers.utils.helpers import create_causal_mask from deepsparse.v2.operators import Operator +from deepsparse.v2.text_generation.nl_engine_operator import NlEngineInput +from deepsparse.v2.utils import Context, InferenceState, PipelineState + + +__all__ = ["MultiEnginePrefill"] + + +class OnnxInputNames(Enum): + INPUT_IDS = "input_ids" + ATTN_MASK = "attention_mask" + CAUSAL_MASK = "causal_mask" + POSITIONS = "positions" class MultiEnginePrefill(Operator): def __init__(self, prompt_sequence_length): self.prompt_sequence_length = prompt_sequence_length self.sequence_length = sequence_length - self.onnx_input_names_no_cache = onnx_input_names_no_cache - - def run(self, inp: Any, context: Optional[Context]): - ## process token_batch - ## return to run through multi_engie; store prompt logits + tokens processed (pipeline variable?) - ## who is looping through the token batches and keeping track? + self.cases = { + OnnxInputNames.ATTN_MASK.value: self._case_attn_mask, + OnnxInputNames.POSITIONS.value: self._case_positions, + } + + # potentially update to use context + def can_operate(self, inp: Any, context: Context, inference_state: InferenceState): + if len(inp.tokens) < self.prompt_sequence_length: + return False + + start_token = inference_state.current_state.get("start_token") + end_token = inference_state.current_state.get("end_token") + + if ( + end_token - start_token == self.prompt_sequence_length + and inference_state.current_state.get("batches_processed") + < inference_state.current_state.get("num_batches") + ): + return True + return False + + def _case_attn_mask(self, num_total_processed_tokens: int): + # create an empty attention mask + engine_input = numpy.zeros((1, self.sequence_length), dtype=numpy.int64) + # calculate the number of entries in attention mask that should be set to 1 + num_attention_entries_to_unmask = min( + num_total_processed_tokens + self.prompt_sequence_length, + self.sequence_length, + ) + engine_input[:, -num_attention_entries_to_unmask:] = 1 + return engine_input + + def _case_positions(self, num_total_processed_tokens: int): + return ( + numpy.arange( + num_total_processed_tokens, + num_total_processed_tokens + self.prompt_sequence_length, + ) + .reshape(1, -1) + .astype(numpy.int64) + ) + + def _fetch_state_update(self, current: dict): + return { + "start_token": current.get("end_token"), + "end_token": current.get("end_token") + self.prompt_sequence_length, + "batches_processed": current.get("batches_processed") + 1, + "num_tokens_processed": current.get("num_tokens_processed") + + self.prompt_sequence_length, + } + + def run( + self, + inp: Any, + context: Optional[Context], + pipeline_state: PipelineState, + inference_state: InferenceState, + ): + tokens = inp.get("tokens") + kv_cache = inp.get("kv_cache") + onnx_input_names_no_cache = pipeline_state.current_state.get( + "onnx_input_names_no_cache" + ) + current = inference_state.current_state + start = current.get("start_multi_token") + end = current.get("end_multi_token") + token_batch = tokens[start:end] + + num_total_processed_tokens = kv_cache.total_num_processed_tokens + engine_inputs = [] + for name in onnx_input_names_no_cache: + if name == OnnxInputNames.INPUT_IDS.value: + engine_inputs.append(numpy.array([token_batch])) + elif ( + name == OnnxInputNames.ATTN_MASK.value + or name == OnnxInputNames.POSITIONS.value + ): + engine_inputs.append(self.cases[name](num_total_processed_tokens)) + # create the causal mask once we have the input_ids and attention_mask + if OnnxInputNames.CAUSAL_MASK.value in onnx_input_names_no_cache: + causal_mask = create_causal_mask( + input_ids=engine_inputs[0], attention_mask=engine_inputs[1] + ) + engine_inputs.append(causal_mask) + # update state for next token batch to process + state_update = self._fetch_state_update(current) + return { + "engine_inputs": engine_inputs, + "kv_cache": kv_cache, + "tokens": tokens, + }, state_update diff --git a/src/deepsparse/v2/text_generation/nl_engine_operator.py b/src/deepsparse/v2/text_generation/nl_engine_operator.py index b4ad941882..8630b2db8d 100644 --- a/src/deepsparse/v2/text_generation/nl_engine_operator.py +++ b/src/deepsparse/v2/text_generation/nl_engine_operator.py @@ -8,8 +8,9 @@ __all__ = ["NLEngineOperator"] class NlEngineInput(BaseModel): - inputs: Any = Field(description="engine inputs") - kv_cache: DecoderKVCache = Field(description="kv_cache object") + engine_inputs: Any = Field(description="engine inputs") + kv_cache: Any = Field(description="kv_cache object") # DecoderKVCache + tokens: Any = Field(description="tokens") class NLEngineOperator(EngineOperator): @@ -34,7 +35,9 @@ def __init__(self, input_ids_length=input_ids_length, ) - self._can_operate = enable_multitoken_prefill + if not kwargs.get("engine_kwargs"): + engine_kwargs = {} + self.kv_cache_data_type = None if any(output_indices_to_be_cached): self.kv_cache_data_type = kv_cache_data_type @@ -52,10 +55,6 @@ def __init__(self, self.sequence_length = sequence_length self.input_ids_length = input_ids_length - @property - def can_operate(self): - return self._can_operate - def run(self, inp: NlEngineInput, context: Optional[Context]) -> Any: engine_input = inp.inputs kv_cache = inp.kv_cache @@ -73,15 +72,24 @@ def run(self, inp: NlEngineInput, context: Optional[Context]) -> Any: ) else: # run the engine without the LIB.kv_cache object - out = super().__call__(inputs) - + out, _ = super().run( + inputs, + context=context, + pipeline_state=pipeline_state, + inference_state=inference_state, + ) + logits, *kv_cache_state = out self._update_kv_cache( kv_cache_state=kv_cache_state, input_ids_len=self.input_ids_length, kv_cache=kv_cache, ) - return logits + + output = dict(inp) + output.update({"logits": logits, "kv_cache": kv_cache}) + + return output, {} def _add_kv_cache_to_input(self, engine_input, kv_cache): kv_cache_state = copy.copy(kv_cache.cached_inputs) diff --git a/src/deepsparse/v2/text_generation/pipeline.py b/src/deepsparse/v2/text_generation/pipeline.py index 4c9cbf7090..5d5ad70bb9 100644 --- a/src/deepsparse/v2/text_generation/pipeline.py +++ b/src/deepsparse/v2/text_generation/pipeline.py @@ -1,47 +1,215 @@ -from deepsparse.v2 import pipeline - -class TextGenerationPipeline(Pipeline): - def __init__(self, prompt_sequence_length, enable_multitoken_prefill): - transformers_preprocess = TransformersPreprocess() ## set-up config/tokenizer - - single_engine_operator = NLEngineOperator() # set-up engine - multi_engine_operator = NLEngineOperator(enable_multitoken_prefill) # set-up engine - - input_preprocess = Preprocess() # take in config/produce input_tokens, update context variable - tokens_to_engine_input = TokenToEngineInput() ## convert input_tokens to engine_input (depends on if multi-token available) - - - prefill_preprocess = PrefillPreprocess() - ## schema with engine specific values? or the engine itself? - # class variables? - # get tokens based on attn mask, set-up kv_cache, return both - - ## Update the schema as part of the run function in the pipeline? - ## that would put the ownness on the pipeline to be able to check which engine can run/is valid - - - - - ops = {"input_preprocess": input_preprocess, - "prefill_preprovess": prefill_preprocess, - "singe_engine_operator": single_engine_operator, - "multi_engine_operator": multi_engine_operator} - - routes = {"preprocess": {"input_preprocess", "token_to_engine_input"}} - - +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Dict + +from deepsparse.transformers.utils.helpers import process_generation_config +from deepsparse.utils import join_engine_outputs, split_engine_inputs +from deepsparse.v2.operators import Operator +from deepsparse.v2.pipeline import Pipeline +from deepsparse.v2.routers import TextGenerationRouter +from deepsparse.v2.schedulers import OperatorScheduler +from deepsparse.v2.text_generation import ( + AutoRegressiveOperator, + CompilePromptLogits, + KVCacheCreator, + MultiEnginePrefill, + NLEngineOperator, + PrepareforMultiEngine, + PrepareforPrefill, + PrepareforSingleEngine, + ProcessInputsTextGeneration, + TokensToEngineInputs, +) +from deepsparse.v2.utils import PipelineState - - - - - - - - - - - - - +class TextGenerationPipeline(Pipeline): + def __init__( + self, + model_path: str, + prompt_sequence_length: int = 16, + sequence_length: int = 1024, + internal_kv_cache: bool = True, + force_max_tokens: bool = False, + generation_config=None, + engine_kwargs: Dict = None, + ): + + pipeline_state = PipelineState() + pipeline_state_vals = {} + + # transformers_preprocess = TransformersPreprocess() ## set-up config/tokenizer ## should give us a tokenizer, onnxfilepath + # temporarily copy/pasta transformers code until this operator is set-up + self.tokenizer = None + model_path = self.setup_onnx_file_path(model_path, sequence_length) + self.tokenizer.pad_token = self.tokenizer.eos_token + + if not engine_kwargs: + engine_kwargs = {} + engine_kwargs["model_path"] = model_path + + if internal_kv_cache and engine_kwargs.get("engine_type") == "onnxruntime": + internal_kv_cache = False + + single_engine_operator = NLEngineOperator( + sequence_length=sequence_length, + internal_kv_cache=internal_kv_cache, + input_ids_length=prompt_sequence_length, + **engine_kwargs, + ) + + multi_engine_operator = NLEngineOperator( + sequence_length=sequence_length, + internal_kv_cache=internal_kv_cache, + input_ids_length=1, + **engine_kwargs, + ) + + pipeline_state_vals[ + "onnx_input_names_no_cache" + ] = single_engine_operator.onnx_input_names_no_cache + pipeline_state_vals["cache_shape"] = single_engine_operator.cache_shape + pipeline_state_vals["output_names"] = single_engine_operator.output_names + pipeline_state_vals[ + "kv_cache_data_type" + ] = single_engine_operator.kv_cache_data_type + pipeline_state.create_state(pipeline_state_vals) + + # Can have the transformers call the operator inside this operator --> need tokenzier and model_path, fed into engine + process_inputs = ProcessInputsTextGeneration( + generation_config=process_generation_config(generation_config), + sequence_length=sequence_length, + tokenizer=self.tokenizer, + ) + + kv_cache_creator = KVCacheCreator( + sequence_length=sequence_length, + tokenizer=self.tokenizer, + prompt_sequence_length=prompt_sequence_length, + internal_kv_cache=internal_kv_cache, + ) + + # Operators has a dependency on the engine_operator as depends on the engine + # We can either initialize or we store everything in a pipeline state (this couples the operators together) + tokens_to_engine_input = TokensToEngineInputs() + engine_inputs_for_prefill = PrepareforPrefill(kv_cache_creator=kv_cache_creator) + prepare_for_multi_engine = PrepareforMultiEngine( + prompt_sequence_length=prompt_sequence_length + ) + multi_engine_prefill = MultiEnginePrefill( + prompt_sequence_length=prompt_sequence_length, + sequence_length=sequence_length, + ) + compile_prompt_logits = CompilePromptLogits() + prep_for_single_engine = PrepareforSingleEngine() + autoregressive_preprocess = AutoRegressiveOperator( + sequence_length=sequence_length + ) + + ops = { + "process_input": process_inputs, + "single_engine": single_engine_operator, + "multi_engine": multi_engine_operator, + "kv_cache_creator": kv_cache_creator, + "tokens_to_engine": tokens_to_engine_input, + "prepare_prefill": engine_inputs_for_prefill, + "prepare_multiengine": prepare_for_multi_engine, + "multi_engine_prefill": multi_engine_prefill, + "compile_logits": compile_prompt_logits, + "prepare_single_engine": prep_for_single_engine, + "autoregressive_preprocess": autoregressive_preprocess, + } + + routes = { + "process_input": "tokens_to_engine", + "tokens_to_engine": "prepare_prefill", + "prepare_prefill": ["prepare_multiengine", "prepare_single_engine"], + "prepare_multiengine": "multi_engine_prefill", + "multi_engine_prefill": "multi_engine", + "multi_engine": "compile_logits", + "compile_logits": [ + "multi_engine_prefill", + "prepare_single_engine", + "autoregressive_process", + "STOP", + ], + "prepare_single_engine": "autoregressive_preprocess", + "autoregressive_preprocess": "single_engine", + "single_engine": "compile_logits", + } + + router = TextGenerationRouter( + end_route="STOP", start_route="process_input", route=routes + ) + scheduler = [OperatorScheduler()] + super().__init__( + ops=ops, router=router, schedulers=scheduler, pipeline_state=pipeline_state + ) + + """ + def expand_inputs(self, **kwargs): + inp = kwargs.get("inp") + engine_inputs = inp.engine_inputs + return split_engine_inputs(engine_inputs, self.batch_size) + + def combine_inputs(self, **kwargs): + batch_outputs = kwargs.get("batch_outputs") + orig_batch_size = kwargs.get("orig_batch_size") + return join_engine_outputs(batch_outputs, orig_batch_size) + """ + + # stealing this for now + def setup_onnx_file_path(self, model_path, sequence_length) -> str: + import logging + + import transformers + from transformers import AutoTokenizer + + from deepsparse.transformers.helpers import get_deployment_path + + """ + Parses ONNX model from the `model_path` provided. It additionally + creates config and tokenizer objects from the `deployment path`, + derived from the `model_path` provided. + + :return: file path to the processed ONNX file for the engine to compile + """ + deployment_path, onnx_path = get_deployment_path(model_path) + + # temporarily set transformers logger to ERROR to avoid + # printing misleading warnings + hf_logger = logging.getLogger("transformers") + hf_logger_level = hf_logger.level + hf_logger.setLevel(logging.ERROR) + self.config = transformers.PretrainedConfig.from_pretrained( + deployment_path, + finetuning_task=self.task if hasattr(self, "task") else None, + ) + hf_logger.setLevel(hf_logger_level) + + self._trust_remote_code = False + self.tokenizer = AutoTokenizer.from_pretrained( + deployment_path, + trust_remote_code=self._trust_remote_code, + model_max_length=sequence_length, + ) + + if not self.config or not self.tokenizer: + raise RuntimeError( + "Invalid config or tokenizer provided. Please provide " + "paths to the files or ensure they exist in the `model_path` provided. " + "See `tokenizer` and `config` arguments for details." + ) + return onnx_path diff --git a/src/deepsparse/v2/text_generation/prep_for_prefill.py b/src/deepsparse/v2/text_generation/prep_for_prefill.py new file mode 100644 index 0000000000..e1d1114bac --- /dev/null +++ b/src/deepsparse/v2/text_generation/prep_for_prefill.py @@ -0,0 +1,54 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Any, Optional + +from pydantic import BaseModel, Field + +from deepsparse.transformers.utils import DecoderKVCache +from deepsparse.v2.operators import Operator +from deepsparse.v2.utils import Context, InferenceState, PipelineState + + +__all__ = ["PrepareforPrefill"] + + +class PrepareforPrefill(Operator): + def __init__(self, kv_cache_creator): + self.kv_cache_creator = kv_cache_creator + + def run( + self, + inp: Any, + context: Optional[Context], + pipeline_state: PipelineState, + inference_state: InferenceState, + ): + cache_shape = pipeline_state.current_state.get("cache_shape") + data_type = pipeline_state.current_state.get("kv_cache_data_type") + output_names = pipeline_state.current_state.get("output_names") + + engine_inputs = inp.get("engine_inputs") + kv_cache, _ = self.kv_cache_creator( + context=context, + pipeline_state=pipeline_state, + inference_state=inference_state, + **{ + "cache_shape": cache_shape, + "kv_cache_data_type": data_type, + "output_names": output_names, + }, + ) + tokens = engine_inputs[0][engine_inputs[1].nonzero()].tolist() + return {"tokens": tokens, "kv_cache": kv_cache.kv_cache}, {} diff --git a/src/deepsparse/v2/text_generation/prep_for_single_engine.py b/src/deepsparse/v2/text_generation/prep_for_single_engine.py new file mode 100644 index 0000000000..73a2144fbb --- /dev/null +++ b/src/deepsparse/v2/text_generation/prep_for_single_engine.py @@ -0,0 +1,65 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Any, Optional + +from pydantic import Field + +from deepsparse.v2.operators import Operator +from deepsparse.v2.utils import Context, InferenceState, PipelineState + + +__all__ = ["PrepareforSingleEngine"] + + +class PrepareforSingleEngine(Operator): + def can_operate(self, inp: Any, context: Context, inference_state: InferenceState): + number_tokens_processed = inference_state.current_state.get( + "num_tokens_processed" + ) + tokens = inp.get("tokens") + if ( + len(tokens) < self.prompt_sequence_length + ): ## can't run multi-engine (running first time) + return True + + for c in context.stages_executed: + if c.operator.__name__ == "AutoRegressiveOperator": + return False + + if ( + len(tokens[number_tokens_processed:]) == 0 + ): ## if 0 remain, can't operate (after multi-engine has already run) + return False + return True ## if some remain, can operate + + def run( + self, + inp: Any, + context: Optional[Context], + pipeline_state: PipelineState, + inference_state: InferenceState, + ): + tokens = inp.get("tokens") + num_processed_tokens = inference_state.current_stat.get( + "num_tokens_processed", 0 + ) + state_dict = { + "num_batches": len(tokens[num_processed_tokens:]), + "start_token": num_processed_tokens, + "end_token": num_processed_tokens + 1, + "batches_processed": 0, + "num_processed_tokens": num_processed_tokens, + } + return inp, state_dict diff --git a/src/deepsparse/v2/text_generation/process_inputs.py b/src/deepsparse/v2/text_generation/process_inputs.py new file mode 100644 index 0000000000..90c27b7285 --- /dev/null +++ b/src/deepsparse/v2/text_generation/process_inputs.py @@ -0,0 +1,121 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Any, Optional + +from pydantic import BaseModel, Field + +from deepsparse.transformers.pipelines.text_generation import TextGenerationInput +from deepsparse.transformers.utils.helpers import ( + check_and_return_generation_config, + create_causal_mask, + override_config, + repeat_inputs, +) +from deepsparse.v2.operators import Operator +from deepsparse.v2.text_generation.tokens_to_engine_inputs import TokensToEngineInput +from deepsparse.v2.utils import Context, InferenceState, PipelineState + + +class GenerationDefaults: + num_return_sequences = 1 + max_length = 1024 + max_new_tokens = None + output_scores = False + top_k = 0 + top_p = 0.0 + repetition_penalty = 0.0 + do_sample = False + temperature = 1.0 + + +__all__ = ["ProcessInputsTextGeneration"] + + +class ProcessInputsTextGeneration(Operator): + input_schema = TextGenerationInput + output_schema = TokensToEngineInput + + def __init__(self, tokenizer, generation_config, sequence_length): + self.generation_config = generation_config + self.tokenizer = tokenizer + self.sequence_length = sequence_length + + def run( + self, + inp: Any, + context: Optional[Context], + pipeline_state: PipelineState, + inference_state: InferenceState, + ): + generation_config = check_and_return_generation_config( + self.generation_config, inp.generation_config, GenerationDefaults() + ) + + generation_config = override_config(inp.generation_kwargs, generation_config) + + original_inputs = inp.sequences + if generation_config.num_return_sequences > 1: + if isinstance(inp.sequences, str): + inp.sequences = [inp.sequences] + inp.sequences = repeat_inputs( + inp.sequences, generation_config.num_return_sequences + ) + + if inp.fixed_sequences_length: + # to enforce a fixed sequence length, we need to + # truncate the input to the maximum sequence length + # or/and pad it to the maximum sequence length + truncate, padding = True, "max_length" + else: + # otherwise, we do not need to truncate the input + # and we shall can pad it to the longest sequence + # in the batch (so that the engine can process multiple inputs + # at once) + truncate, padding = False, "longest" + + input_tokens = self.tokenizer( + inp.sequences, + return_tensors="np", + max_length=self.sequence_length, + padding=padding, + truncation=truncate, + ) + + attention_mask = input_tokens["attention_mask"] + + positions = attention_mask.cumsum(1) * attention_mask + positions -= 1 # assert that positions start at 0 + + causal_mask = create_causal_mask( + input_tokens["input_ids"], input_tokens["attention_mask"] + ) + + input_tokens = dict( + **input_tokens, positions=positions, causal_mask=causal_mask + ) + + inference_state_update = dict( + prompts=original_inputs, + streaming=inp.streaming, + generation_config=generation_config, + include_prompt_logits=inp.include_prompt_logits, + callback=inp.callback, + stop=inp.stop, + top_p=generation_config.top_p, + top_k=generation_config.top_k, + presence_penalty=inp.presence_penalty, + frequency_penalty=generation_config.repetition_penalty, + ) + return {"tokens": input_tokens}, inference_state_update diff --git a/src/deepsparse/v2/utils/state.py b/src/deepsparse/v2/utils/state.py new file mode 100644 index 0000000000..ee2d71ae0d --- /dev/null +++ b/src/deepsparse/v2/utils/state.py @@ -0,0 +1,52 @@ +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import warnings +from abc import ABC +from typing import Any, Union + + +__all__ = ["State", "PipelineState", "InferenceState"] + + +class State(ABC): + def __init__(self): + self._current_state = None + + @property + def current_state(self): + return self._current_state + + +# Created during pipeline initialization; only read access +class PipelineState(State): + def create_state(self, new_state: dict): + if self._current_state: + raise ValueError("State creation is only allowed during initialization.") + self._current_state = new_state + + +# Should be created during each inference run, similar to the context +class InferenceState(State): + def create_state(self, new_state: dict): + if self._current_state: + warnings.warn("Current state already exists, overriding.") + self._current_state = new_state + + def update_value(self, attribute: str, value: Union[str, int, list]): + if not self._current_state.get(attribute): + raise ValueError(f"{attribute} is not a valid state attribute") + self._current_state[attribute] = value + + def update_state(self, value: Any): + self._current_state.update(value) From 5cf4b3fff2970dd1308ca67e4efa976dbb3fdae4 Mon Sep 17 00:00:00 2001 From: Dipika Sikka Date: Tue, 24 Oct 2023 20:35:49 -0400 Subject: [PATCH 07/12] prompt inference, initial functionality --- src/deepsparse/v2/operators/operator.py | 2 +- src/deepsparse/v2/pipeline.py | 14 ++- src/deepsparse/v2/routers/router.py | 11 +-- src/deepsparse/v2/text_generation/__init__.py | 4 +- .../autoregressive_preprocess_operator.py | 68 +++++++------- .../v2/text_generation/compile_logits.py | 12 ++- .../multi_engine_prefill_operator.py | 63 ++++++------- .../v2/text_generation/nl_engine_operator.py | 62 +++++++++---- src/deepsparse/v2/text_generation/pipeline.py | 90 +++++++++---------- .../v2/text_generation/prep_for_prefill.py | 19 ++-- .../text_generation/prep_for_single_engine.py | 55 ++++++------ .../v2/text_generation/process_inputs.py | 40 +++++---- 12 files changed, 245 insertions(+), 195 deletions(-) diff --git a/src/deepsparse/v2/operators/operator.py b/src/deepsparse/v2/operators/operator.py index 42ff42911e..62591187e0 100644 --- a/src/deepsparse/v2/operators/operator.py +++ b/src/deepsparse/v2/operators/operator.py @@ -103,7 +103,7 @@ def can_operate( self, inp: Any, context: Context, inference_state: InferenceState ) -> bool: """ - Whether or not the given operator can run, based on input and state + Whether or not the given operator can run, based on input, context, or state """ raise NotImplementedError diff --git a/src/deepsparse/v2/pipeline.py b/src/deepsparse/v2/pipeline.py index d2a7acd719..b836dc71ed 100644 --- a/src/deepsparse/v2/pipeline.py +++ b/src/deepsparse/v2/pipeline.py @@ -84,13 +84,25 @@ def run(self, *args, **kwargs): output_future = self._scheduler_group.submit( operator_output, operator=operator ) + + # print("Current State", inference_state.current_state) + + """ + output_future = self._scheduler_group.submit( + operator=operator, + operator_input=inp, + context=context, + pipeline_state=self.pipeline_state, + inference_state=inference_state, + ) + """ # wait for future to resolve operator_output, state_update = output_future.result() inference_state.update_state(state_update) next_step = self.router.next( - next_step, self.ops, operator_output, inference_state + next_step, self.ops, context, operator_output, inference_state ) inp = operator_output diff --git a/src/deepsparse/v2/routers/router.py b/src/deepsparse/v2/routers/router.py index fe394868e2..2acf6c701e 100644 --- a/src/deepsparse/v2/routers/router.py +++ b/src/deepsparse/v2/routers/router.py @@ -18,6 +18,7 @@ from typing import Dict, List, Union from deepsparse.v2.operators import Operator +from deepsparse.v2.utils import Context, InferenceState _LOGGER = logging.getLogger(__name__) @@ -39,7 +40,6 @@ def __init__(self, end_route: Union[str, int], start_route: Union[str, int]): self.START_ROUTE = start_route self.END_ROUTE = end_route self.route = route - self.SPLIT_ROUTE = None @abstractmethod def next( @@ -51,6 +51,8 @@ def next( :param past: the previous index or key. This should uniquely determine the next operator to run :param ops: list or dictionary of operators + :param inp: operator input + :param inference_state: state variables stores in InferenceState :returns: the next index or dictionary key for the next operator to run """ raise NotImplementedError @@ -114,7 +116,7 @@ class TextGenerationRouter(Router): Router for a DAG. Expects graphs be presented in the form of a dictionary, where keys are the nodes of the graph and the values are the connected nodes. For nodes with multiple ouput edges, all the nodes will be visited and the first node - where `can_operate` returns True will run. + where `can_operate` returns True will run. Paths should be deterministic. """ def __init__(self, end_route: str, start_route: str, route: Dict): @@ -124,18 +126,17 @@ def next( self, past: str, ops: Dict[str, Operator], + context: Context, inp: Any, inference_state: InferenceState, ) -> int: node = past if isinstance(self.route[node], str): - print(past, self.route[node]) return self.route[node] else: for neighbour_node in self.route[node]: neighbour_node_op = ops[neighbour_node] - print(node, neighbour_node) - if neighbour_node_op.can_operate(inp, inference_state): + if neighbour_node_op.can_operate(inp, context, inference_state): return neighbour_node raise ValueError("Cannot operate on any of the nodes") diff --git a/src/deepsparse/v2/text_generation/__init__.py b/src/deepsparse/v2/text_generation/__init__.py index 7d25447729..2c6c351c17 100644 --- a/src/deepsparse/v2/text_generation/__init__.py +++ b/src/deepsparse/v2/text_generation/__init__.py @@ -13,7 +13,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +# flake8: noqa from .autoregressive_preprocess_operator import * from .compile_logits import * from .kv_cache_operator import * @@ -21,9 +21,7 @@ from .nl_engine_operator import * from .prep_for_prefill import * from .prep_for_single_engine import * -from .prepare_for_multi_engine import * from .process_inputs import * -from .tokens_to_engine_inputs import * from .pipeline import * # isort:skip diff --git a/src/deepsparse/v2/text_generation/autoregressive_preprocess_operator.py b/src/deepsparse/v2/text_generation/autoregressive_preprocess_operator.py index b6db85379e..80329028fc 100644 --- a/src/deepsparse/v2/text_generation/autoregressive_preprocess_operator.py +++ b/src/deepsparse/v2/text_generation/autoregressive_preprocess_operator.py @@ -21,34 +21,40 @@ from deepsparse.v2.utils import Context, InferenceState, PipelineState -__all__ = ["AutoRegressiveOperator"] +__all__ = ["AutoRegressiveOperatorPreprocess"] -class AutoRegressiveOperator(Operator): - def __init__(self, sequence_length: int): +class AutoRegressiveOperatorPreprocess(Operator): + def __init__(self, sequence_length: int, prompt_sequence_length: int): + """ + Prepare the tokens for the single-token engine. This requires creating the + attention mask, positions, and causal mask. The output contains these three + arrays to be passed into the single-token engine. + """ self.sequence_length = sequence_length + self.prompt_sequence_length = prompt_sequence_length def can_operate(self, inp: Any, context: Context, inference_state: InferenceState): - if len(inp.tokens) > self.prompt_sequence_length: - return False + """ + Can run this Operator if the number of tokens left to process is greater than + 0 but less than the self.promt_sequence_length. Also, thie Operator can only + run after PrepareforSingleEngine as it requires the kv_cache to be updated. + """ + tokens = inp.get("tokens") + kv_cache = inp.get("kv_cache") - start_token = inference_state.current_state.get("start_token") - end_token = inference_state.current_state.get("end_token") + found = False + for c in context.stages_executed: + if c.operator.__class__.__name__ == "PrepareforSingleEngine": + found = True - if end_token - start_token == 1 and inference_state.current_state.get( - "batches_processed" - ) < inference_state.current_state.get("num_batches"): + remaining_tokens = len(tokens) - kv_cache.total_num_processed_tokens + if found and ( + remaining_tokens > 0 and remaining_tokens < self.prompt_sequence_length + ): return True return False - def _fetch_state_update(self, current: dict): - return { - "start_token": current.get("end_token"), - "end_token": current.get("end_token") + 1, - "batches_processed": current.get("batches_processed") + 1, - "num_tokens_processed": current.get("num_tokens_processed") + 1, - } - def run( self, inp: Any, @@ -59,20 +65,8 @@ def run( kv_cache = inp.get("kv_cache") tokens = inp.get("tokens") - start_token = inference_state.current_state.get("start_token") - end_token = inference_state.current_state.get("end_token") - engine_input_names = pipeline_state.current_state.get( - "onnx_input_names_no_cache" - ) - - new_token = tokens[start_token:end_token] - num_total_processed_tokens = ( - kv_cache.total_num_processed_tokens - ) # should be same as the state value? - print( - num_total_processed_tokens, - inference_state.current_state.get("num_total_processed_tokens"), - ) + num_total_processed_tokens = kv_cache.total_num_processed_tokens + new_token = tokens[num_total_processed_tokens] # padding is added to left, so attention mask is 1s from the # right up to the number of total tokens (prompt + generated) @@ -85,7 +79,6 @@ def run( input_ids = numpy.array([[new_token]]) causal_mask = create_causal_mask(input_ids, attention_mask) - # filter out the inputs that are not needed by the engine engine_inputs_map = dict( input_ids=input_ids, attention_mask=attention_mask, @@ -97,12 +90,13 @@ def run( engine_inputs_map[name] for name in engine_input_names ] - # covered by tokens_to_engine_names --> why does this have to be a dictionary? can also remove tokens_to_engins? - engine_inputs = [engine_inputs_map[name] for name in engine_input_names] + onnx_input_names_no_cache = pipeline_state.current_state.get( + "onnx_input_names_no_cache" + ) + engine_inputs = [engine_inputs_map[name] for name in onnx_input_names_no_cache] - state_update = self._fetch_state_update(inference_state.current_state) return { "engine_inputs": engine_inputs, "kv_cache": kv_cache, "tokens": tokens, - }, state_update + }, {} diff --git a/src/deepsparse/v2/text_generation/compile_logits.py b/src/deepsparse/v2/text_generation/compile_logits.py index 5c9f12af19..a7131da105 100644 --- a/src/deepsparse/v2/text_generation/compile_logits.py +++ b/src/deepsparse/v2/text_generation/compile_logits.py @@ -15,7 +15,7 @@ from typing import Any, Optional -from pydantic import BaseModel, Field +import numpy as np from deepsparse.v2.operators import Operator from deepsparse.v2.utils import Context, InferenceState, PipelineState @@ -25,6 +25,12 @@ class CompilePromptLogits(Operator): + """ + Combine the prompt logits. Currently relying on the inference state to store the + prompt logits for each token or multi-token batch processed. This operator will + take prompt logits from each iteration run and update the inference state. + """ + def run( self, inp: Any, @@ -37,9 +43,9 @@ def run( if inference_state.current_state.get(logit_type) is not None: current_logits = inference_state.current_state.get(logit_type).copy() - current_logits.extend(logits) + current_logits = np.concatenate((current_logits, logits), axis=1) else: - current_logits = list(logits) + current_logits = logits state_update = {logit_type: current_logits} return inp, state_update diff --git a/src/deepsparse/v2/text_generation/multi_engine_prefill_operator.py b/src/deepsparse/v2/text_generation/multi_engine_prefill_operator.py index dafc282bde..27752cf04b 100644 --- a/src/deepsparse/v2/text_generation/multi_engine_prefill_operator.py +++ b/src/deepsparse/v2/text_generation/multi_engine_prefill_operator.py @@ -19,7 +19,6 @@ from deepsparse.transformers.utils.helpers import create_causal_mask from deepsparse.v2.operators import Operator -from deepsparse.v2.text_generation.nl_engine_operator import NlEngineInput from deepsparse.v2.utils import Context, InferenceState, PipelineState @@ -33,8 +32,17 @@ class OnnxInputNames(Enum): POSITIONS = "positions" +# NOTE: A possible clean-up could involve combining this Operator and the +# autoregressive_preprocess_operator + + class MultiEnginePrefill(Operator): - def __init__(self, prompt_sequence_length): + def __init__(self, prompt_sequence_length, sequence_length): + """ + Prepare the tokens for the multi-token engine. This requires creating the + attention mask, positions, and causal mask. The output contains these three + arrays to be passed into the multi-token engine. + """ self.prompt_sequence_length = prompt_sequence_length self.sequence_length = sequence_length self.cases = { @@ -42,18 +50,20 @@ def __init__(self, prompt_sequence_length): OnnxInputNames.POSITIONS.value: self._case_positions, } - # potentially update to use context def can_operate(self, inp: Any, context: Context, inference_state: InferenceState): - if len(inp.tokens) < self.prompt_sequence_length: - return False + """ + Can only run if the number of prompt tokens left to process is greater than + or equal to the self.prompt_sequence_length. + """ + kv_cache = inp.get("kv_cache") + tokens = inp.get("tokens") - start_token = inference_state.current_state.get("start_token") - end_token = inference_state.current_state.get("end_token") + if len(tokens) < self.prompt_sequence_length: + return False if ( - end_token - start_token == self.prompt_sequence_length - and inference_state.current_state.get("batches_processed") - < inference_state.current_state.get("num_batches") + len(tokens) - kv_cache.total_num_processed_tokens + >= self.prompt_sequence_length ): return True return False @@ -79,15 +89,6 @@ def _case_positions(self, num_total_processed_tokens: int): .astype(numpy.int64) ) - def _fetch_state_update(self, current: dict): - return { - "start_token": current.get("end_token"), - "end_token": current.get("end_token") + self.prompt_sequence_length, - "batches_processed": current.get("batches_processed") + 1, - "num_tokens_processed": current.get("num_tokens_processed") - + self.prompt_sequence_length, - } - def run( self, inp: Any, @@ -100,33 +101,35 @@ def run( onnx_input_names_no_cache = pipeline_state.current_state.get( "onnx_input_names_no_cache" ) - current = inference_state.current_state - start = current.get("start_multi_token") - end = current.get("end_multi_token") - token_batch = tokens[start:end] num_total_processed_tokens = kv_cache.total_num_processed_tokens + start = num_total_processed_tokens + end = start + self.prompt_sequence_length + token_batch = tokens[start:end] + engine_inputs = [] for name in onnx_input_names_no_cache: if name == OnnxInputNames.INPUT_IDS.value: - engine_inputs.append(numpy.array([token_batch])) + engine_input = numpy.array([token_batch]) elif ( name == OnnxInputNames.ATTN_MASK.value or name == OnnxInputNames.POSITIONS.value ): - engine_inputs.append(self.cases[name](num_total_processed_tokens)) + engine_input = self.cases[name](num_total_processed_tokens) + elif name == OnnxInputNames.CAUSAL_MASK.value: + continue + + engine_inputs.append(engine_input) - # create the causal mask once we have the input_ids and attention_mask if OnnxInputNames.CAUSAL_MASK.value in onnx_input_names_no_cache: causal_mask = create_causal_mask( - input_ids=engine_inputs[0], attention_mask=engine_inputs[1] + input_ids=engine_inputs[0], + attention_mask=engine_inputs[1], ) engine_inputs.append(causal_mask) - # update state for next token batch to process - state_update = self._fetch_state_update(current) return { "engine_inputs": engine_inputs, "kv_cache": kv_cache, "tokens": tokens, - }, state_update + }, {} diff --git a/src/deepsparse/v2/text_generation/nl_engine_operator.py b/src/deepsparse/v2/text_generation/nl_engine_operator.py index 8630b2db8d..1422c50f44 100644 --- a/src/deepsparse/v2/text_generation/nl_engine_operator.py +++ b/src/deepsparse/v2/text_generation/nl_engine_operator.py @@ -1,21 +1,48 @@ -from deepsparse.v2.operators import EngineOperator, Operator -from deepsparse import Context as EngineContext -from typing import Any -from deepsparse.v2.utils import Context +# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import copy +import os +from typing import Any, List, Optional, Tuple + from pydantic import BaseModel, Field -from deepsparse.utils.onnx import CACHE_INPUT_PREFIX, + +from deepsparse.utils.onnx import ( + CACHE_INPUT_PREFIX, + overwrite_onnx_model_inputs_for_kv_cache_models, +) +from deepsparse.v2.operators.engine_operator import DEEPSPARSE_ENGINE, EngineOperator +from deepsparse.v2.utils import Context, InferenceState, PipelineState + __all__ = ["NLEngineOperator"] class NlEngineInput(BaseModel): - engine_inputs: Any = Field(description="engine inputs") - kv_cache: Any = Field(description="kv_cache object") # DecoderKVCache - tokens: Any = Field(description="tokens") + engine_inputs: List = Field(description="engine inputs") + kv_cache: Any = Field(description="kv_cache object") + tokens: List = Field(description="tokens") class NLEngineOperator(EngineOperator): + """ + Operator for the NL Decoder Engine. This Operator inherits from the EngineOperator. + Specific updates to engine attributes are made through this operator, as well + as updating the kv_cache. This Operator is used for both the single-token and + multi-token case. + """ + input_schema = NlEngineInput - output_schema = None def __init__(self, sequence_length: int, @@ -24,6 +51,7 @@ def __init__(self, internal_kv_cache: bool = False, **kwargs): + self.kv_cache_data_type = None ( onnx_file_path, output_indices_to_be_cached, @@ -35,17 +63,17 @@ def __init__(self, input_ids_length=input_ids_length, ) - if not kwargs.get("engine_kwargs"): - engine_kwargs = {} + engine_kwargs = kwargs.get("engine_kwargs", {}) + if kwargs.get("engine_type", DEEPSPARSE_ENGINE) == DEEPSPARSE_ENGINE: + if "WAND_OPT_FLAGS" not in os.environ: + os.environ["WAND_OPT_FLAGS"] = "default,~pyramids" - self.kv_cache_data_type = None if any(output_indices_to_be_cached): self.kv_cache_data_type = kv_cache_data_type - if internal_kv_cache and kwargs.get("engine_type") == DEEPSPARSE_ENGINE: - # inform the engine, that are using the kv cache - engine_kwargs = kwargs.get("engine_kwargs") - if not engine_kwargs: - engine_kwargs = {} + if ( + internal_kv_cache + and kwargs.get("engine_type", DEEPSPARSE_ENGINE) == DEEPSPARSE_ENGINE + ): engine_kwargs["cached_outputs"] = output_indices_to_be_cached kwargs["engine_kwargs"] = engine_kwargs diff --git a/src/deepsparse/v2/text_generation/pipeline.py b/src/deepsparse/v2/text_generation/pipeline.py index 5d5ad70bb9..05f564353b 100644 --- a/src/deepsparse/v2/text_generation/pipeline.py +++ b/src/deepsparse/v2/text_generation/pipeline.py @@ -15,22 +15,19 @@ from typing import Dict from deepsparse.transformers.utils.helpers import process_generation_config -from deepsparse.utils import join_engine_outputs, split_engine_inputs from deepsparse.v2.operators import Operator from deepsparse.v2.pipeline import Pipeline from deepsparse.v2.routers import TextGenerationRouter from deepsparse.v2.schedulers import OperatorScheduler from deepsparse.v2.text_generation import ( - AutoRegressiveOperator, + AutoRegressiveOperatorPreprocess, CompilePromptLogits, KVCacheCreator, MultiEnginePrefill, NLEngineOperator, - PrepareforMultiEngine, PrepareforPrefill, PrepareforSingleEngine, ProcessInputsTextGeneration, - TokensToEngineInputs, ) from deepsparse.v2.utils import PipelineState @@ -50,11 +47,12 @@ def __init__( pipeline_state = PipelineState() pipeline_state_vals = {} - # transformers_preprocess = TransformersPreprocess() ## set-up config/tokenizer ## should give us a tokenizer, onnxfilepath - # temporarily copy/pasta transformers code until this operator is set-up + # TODO: The code below will be replaced with a transformers set-up Operator. self.tokenizer = None model_path = self.setup_onnx_file_path(model_path, sequence_length) - self.tokenizer.pad_token = self.tokenizer.eos_token + self.tokenizer.padding_side = "left" + if not self.tokenizer.pad_token: + self.tokenizer.pad_token = self.tokenizer.eos_token if not engine_kwargs: engine_kwargs = {} @@ -66,28 +64,29 @@ def __init__( single_engine_operator = NLEngineOperator( sequence_length=sequence_length, internal_kv_cache=internal_kv_cache, - input_ids_length=prompt_sequence_length, + input_ids_length=1, **engine_kwargs, ) multi_engine_operator = NLEngineOperator( sequence_length=sequence_length, internal_kv_cache=internal_kv_cache, - input_ids_length=1, + input_ids_length=prompt_sequence_length, **engine_kwargs, ) + # NOTE: Currently using pipeline state. Can swap to simply pass in the + # attributes to the specific Operator that neeed them, as class attributes. pipeline_state_vals[ "onnx_input_names_no_cache" - ] = single_engine_operator.onnx_input_names_no_cache - pipeline_state_vals["cache_shape"] = single_engine_operator.cache_shape - pipeline_state_vals["output_names"] = single_engine_operator.output_names + ] = multi_engine_operator.onnx_input_names_no_cache + pipeline_state_vals["cache_shape"] = multi_engine_operator.cache_shape + pipeline_state_vals["output_names"] = multi_engine_operator.output_names pipeline_state_vals[ "kv_cache_data_type" - ] = single_engine_operator.kv_cache_data_type + ] = multi_engine_operator.kv_cache_data_type pipeline_state.create_state(pipeline_state_vals) - # Can have the transformers call the operator inside this operator --> need tokenzier and model_path, fed into engine process_inputs = ProcessInputsTextGeneration( generation_config=process_generation_config(generation_config), sequence_length=sequence_length, @@ -101,53 +100,54 @@ def __init__( internal_kv_cache=internal_kv_cache, ) - # Operators has a dependency on the engine_operator as depends on the engine - # We can either initialize or we store everything in a pipeline state (this couples the operators together) - tokens_to_engine_input = TokensToEngineInputs() + # NOTE: Can also have the KVCacheCreator be initialized inside this Operator. + # Relies on pipeline state variables set-up above (can be swapped to be class + # attributes instead of using the state. engine_inputs_for_prefill = PrepareforPrefill(kv_cache_creator=kv_cache_creator) - prepare_for_multi_engine = PrepareforMultiEngine( - prompt_sequence_length=prompt_sequence_length - ) + multi_engine_prefill = MultiEnginePrefill( prompt_sequence_length=prompt_sequence_length, sequence_length=sequence_length, ) compile_prompt_logits = CompilePromptLogits() - prep_for_single_engine = PrepareforSingleEngine() - autoregressive_preprocess = AutoRegressiveOperator( - sequence_length=sequence_length + prep_for_single_engine = PrepareforSingleEngine( + prompt_sequence_length=prompt_sequence_length, + sequence_length=sequence_length, + ) + autoregressive_preprocess = AutoRegressiveOperatorPreprocess( + sequence_length=sequence_length, + prompt_sequence_length=prompt_sequence_length, ) + final_step = FinalStep() ops = { "process_input": process_inputs, "single_engine": single_engine_operator, "multi_engine": multi_engine_operator, "kv_cache_creator": kv_cache_creator, - "tokens_to_engine": tokens_to_engine_input, "prepare_prefill": engine_inputs_for_prefill, - "prepare_multiengine": prepare_for_multi_engine, "multi_engine_prefill": multi_engine_prefill, "compile_logits": compile_prompt_logits, "prepare_single_engine": prep_for_single_engine, "autoregressive_preprocess": autoregressive_preprocess, + "final_step": final_step, } routes = { - "process_input": "tokens_to_engine", - "tokens_to_engine": "prepare_prefill", - "prepare_prefill": ["prepare_multiengine", "prepare_single_engine"], - "prepare_multiengine": "multi_engine_prefill", + "process_input": "prepare_prefill", + "prepare_prefill": ["multi_engine_prefill", "prepare_single_engine"], "multi_engine_prefill": "multi_engine", "multi_engine": "compile_logits", "compile_logits": [ "multi_engine_prefill", "prepare_single_engine", - "autoregressive_process", - "STOP", + "autoregressive_preprocess", + "final_step", ], "prepare_single_engine": "autoregressive_preprocess", "autoregressive_preprocess": "single_engine", "single_engine": "compile_logits", + "final_step": "STOP", } router = TextGenerationRouter( @@ -158,19 +158,7 @@ def __init__( ops=ops, router=router, schedulers=scheduler, pipeline_state=pipeline_state ) - """ - def expand_inputs(self, **kwargs): - inp = kwargs.get("inp") - engine_inputs = inp.engine_inputs - return split_engine_inputs(engine_inputs, self.batch_size) - - def combine_inputs(self, **kwargs): - batch_outputs = kwargs.get("batch_outputs") - orig_batch_size = kwargs.get("orig_batch_size") - return join_engine_outputs(batch_outputs, orig_batch_size) - """ - - # stealing this for now + # TODO: Move to be part of a generic transformers set-up Operator. def setup_onnx_file_path(self, model_path, sequence_length) -> str: import logging @@ -188,8 +176,6 @@ def setup_onnx_file_path(self, model_path, sequence_length) -> str: """ deployment_path, onnx_path = get_deployment_path(model_path) - # temporarily set transformers logger to ERROR to avoid - # printing misleading warnings hf_logger = logging.getLogger("transformers") hf_logger_level = hf_logger.level hf_logger.setLevel(logging.ERROR) @@ -213,3 +199,15 @@ def setup_onnx_file_path(self, model_path, sequence_length) -> str: "See `tokenizer` and `config` arguments for details." ) return onnx_path + + +# NOTE: This is a dummy last step which will be removed. Used as a final step +# for the current routes. +class FinalStep(Operator): + def can_operate(self, *args, **kwargs): + return True + + def run(self, *args, **kwargs): + inference_state = kwargs.get("inference_state") + prompt_logits = inference_state.current_state.get("prompt_logits") + return prompt_logits, {} diff --git a/src/deepsparse/v2/text_generation/prep_for_prefill.py b/src/deepsparse/v2/text_generation/prep_for_prefill.py index e1d1114bac..264b9859c7 100644 --- a/src/deepsparse/v2/text_generation/prep_for_prefill.py +++ b/src/deepsparse/v2/text_generation/prep_for_prefill.py @@ -14,9 +14,6 @@ from typing import Any, Optional -from pydantic import BaseModel, Field - -from deepsparse.transformers.utils import DecoderKVCache from deepsparse.v2.operators import Operator from deepsparse.v2.utils import Context, InferenceState, PipelineState @@ -25,7 +22,15 @@ class PrepareforPrefill(Operator): - def __init__(self, kv_cache_creator): + def __init__(self, kv_cache_creator: Operator): + """ + Operator before prefill. Responsible for creating the kv_cache based on engine + variables. Currently, this operator expects that the kv_cache_creator is + provided during initization and then uses pipeline state to run the + kv_cache_operator. + """ + # NOTE: Alternatively, we can initialize the kv_cache_creater operator here, + # instead of at the pipeline level. self.kv_cache_creator = kv_cache_creator def run( @@ -35,11 +40,12 @@ def run( pipeline_state: PipelineState, inference_state: InferenceState, ): + # NOTE: Can potentially just be class attributes instead of relying on + # pipeline state. cache_shape = pipeline_state.current_state.get("cache_shape") data_type = pipeline_state.current_state.get("kv_cache_data_type") output_names = pipeline_state.current_state.get("output_names") - engine_inputs = inp.get("engine_inputs") kv_cache, _ = self.kv_cache_creator( context=context, pipeline_state=pipeline_state, @@ -50,5 +56,4 @@ def run( "output_names": output_names, }, ) - tokens = engine_inputs[0][engine_inputs[1].nonzero()].tolist() - return {"tokens": tokens, "kv_cache": kv_cache.kv_cache}, {} + return {"tokens": inp.get("tokens"), "kv_cache": kv_cache.kv_cache}, {} diff --git a/src/deepsparse/v2/text_generation/prep_for_single_engine.py b/src/deepsparse/v2/text_generation/prep_for_single_engine.py index 73a2144fbb..a368d90097 100644 --- a/src/deepsparse/v2/text_generation/prep_for_single_engine.py +++ b/src/deepsparse/v2/text_generation/prep_for_single_engine.py @@ -14,8 +14,6 @@ from typing import Any, Optional -from pydantic import Field - from deepsparse.v2.operators import Operator from deepsparse.v2.utils import Context, InferenceState, PipelineState @@ -24,25 +22,34 @@ class PrepareforSingleEngine(Operator): - def can_operate(self, inp: Any, context: Context, inference_state: InferenceState): - number_tokens_processed = inference_state.current_state.get( - "num_tokens_processed" - ) - tokens = inp.get("tokens") - if ( - len(tokens) < self.prompt_sequence_length - ): ## can't run multi-engine (running first time) - return True + def __init__(self, prompt_sequence_length: int, sequence_length: int): + """ + Prepare to use the single_engine Operator for prompt inference. This requires + updating the kv_cache capacity. + """ + self.prompt_sequence_length = prompt_sequence_length + self.sequence_length = sequence_length + def can_operate(self, inp: Any, context: Context, inference_state: InferenceState): + # Don't rerun if in autoregessive loop for c in context.stages_executed: - if c.operator.__name__ == "AutoRegressiveOperator": + if c.operator.__class__.__name__ == "AutoRegressiveOperatorPreprocess": return False + kv_cache = inp.get("kv_cache") + tokens = inp.get("tokens") + # if 0 prompt tokens remain, can't operate (multi-token engine has already run) + if len(tokens) == kv_cache.total_num_processed_tokens == 0: + return False + + # if number of prompt tokens left to process is >= self.prompt_sequnce_length + # should use the multi_token engine. if ( - len(tokens[number_tokens_processed:]) == 0 - ): ## if 0 remain, can't operate (after multi-engine has already run) + len(tokens) - kv_cache.total_num_processed_tokens + >= self.prompt_sequence_length + ): return False - return True ## if some remain, can operate + return True def run( self, @@ -51,15 +58,9 @@ def run( pipeline_state: PipelineState, inference_state: InferenceState, ): - tokens = inp.get("tokens") - num_processed_tokens = inference_state.current_stat.get( - "num_tokens_processed", 0 - ) - state_dict = { - "num_batches": len(tokens[num_processed_tokens:]), - "start_token": num_processed_tokens, - "end_token": num_processed_tokens + 1, - "batches_processed": 0, - "num_processed_tokens": num_processed_tokens, - } - return inp, state_dict + kv_cache = inp.get("kv_cache") + kv_cache.set_capacity(self.sequence_length - 1) + + input_values = dict(inp) + input_values.update({"kv_cache": kv_cache}) + return input_values, {} diff --git a/src/deepsparse/v2/text_generation/process_inputs.py b/src/deepsparse/v2/text_generation/process_inputs.py index 90c27b7285..f4d5855ad7 100644 --- a/src/deepsparse/v2/text_generation/process_inputs.py +++ b/src/deepsparse/v2/text_generation/process_inputs.py @@ -12,19 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Optional +import pathlib +from typing import Any, Dict, Optional, Union -from pydantic import BaseModel, Field +import transformers from deepsparse.transformers.pipelines.text_generation import TextGenerationInput from deepsparse.transformers.utils.helpers import ( check_and_return_generation_config, - create_causal_mask, override_config, repeat_inputs, ) from deepsparse.v2.operators import Operator -from deepsparse.v2.text_generation.tokens_to_engine_inputs import TokensToEngineInput from deepsparse.v2.utils import Context, InferenceState, PipelineState @@ -44,10 +43,23 @@ class GenerationDefaults: class ProcessInputsTextGeneration(Operator): + """ + Input processing operator. Responsible for tokenizing the input, handling the + generation_config (if provided), updating the inference_state for later use, + and returning the tokens for prompt inferece. The expected input is defined by + the input_schema, which for this operator is TextGeneratioInput. + """ + input_schema = TextGenerationInput - output_schema = TokensToEngineInput - def __init__(self, tokenizer, generation_config, sequence_length): + def __init__( + self, + tokenizer: transformers.PreTrainedTokenizerBase, + generation_config: Union[ + str, pathlib.Path, Dict, transformers.GenerationConfig + ], + sequence_length: int, + ): self.generation_config = generation_config self.tokenizer = tokenizer self.sequence_length = sequence_length @@ -93,19 +105,9 @@ def run( truncation=truncate, ) + input_ids = input_tokens["input_ids"] attention_mask = input_tokens["attention_mask"] - positions = attention_mask.cumsum(1) * attention_mask - positions -= 1 # assert that positions start at 0 - - causal_mask = create_causal_mask( - input_tokens["input_ids"], input_tokens["attention_mask"] - ) - - input_tokens = dict( - **input_tokens, positions=positions, causal_mask=causal_mask - ) - inference_state_update = dict( prompts=original_inputs, streaming=inp.streaming, @@ -118,4 +120,6 @@ def run( presence_penalty=inp.presence_penalty, frequency_penalty=generation_config.repetition_penalty, ) - return {"tokens": input_tokens}, inference_state_update + + tokens = input_ids[attention_mask.nonzero()].tolist() + return {"tokens": tokens}, inference_state_update From 1b951dc80ecfc0df71533f95b17a28377b1282a9 Mon Sep 17 00:00:00 2001 From: Dipika Sikka Date: Tue, 24 Oct 2023 20:43:45 -0400 Subject: [PATCH 08/12] remove image; update state docstring --- .../v2/image_classification/buddy.jpeg | Bin 64557 -> 0 bytes src/deepsparse/v2/utils/state.py | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) delete mode 100644 src/deepsparse/v2/image_classification/buddy.jpeg diff --git a/src/deepsparse/v2/image_classification/buddy.jpeg b/src/deepsparse/v2/image_classification/buddy.jpeg deleted file mode 100644 index 496fc207c4562d18661b92c4b98c6a69accab340..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 64557 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

c*~*Ktu8bd;Y(D8o^fa)Ne~Den)++Oji%SZ zc796I%C{{`*qKqu?_2}Jp-pKlC9+lKr~;11p`VKCI!B4oXW^!;T-w{sg#^1Ya%Mhg zBs4S-BStx%@{7l%{b|Tv^;ZWYS?jmr6-!kp$ikNCk3fPvy(i4#;Xrn0EYmC$)h{c*$XhKKQ2~_cp)d!fCZNePfl+I(5 zS~C60QtRzb(oR3(nChB?R}$KD=C+N)WNlCxrdq88;Uv-%KInC#6)H&KQ__RCyA0km zpa|NbSnB+hEd%E?C!W&)UO2TjTQjr(PCC-Lr7J8np$C0Y=i-K@jX{(+#-z`gYIZ&4 zt8$wJK=V?6koWE@n6OguO3*yF4jpLvk~)PT_A@ci^{xDRo0hytjY`Vp3KvK&+C1EB zeY1rF7zqgPrFM_Dke?<{Rl-O4tPe9U$u`e76qiMbkbNT%0VG645ZkNs5>i%_ zfgq>mwX=D%ay z)KgE&6cC~mlaNj)+M~U6U0WTng$yK~C;tGNfxM`W)%2}uNeaRA&p}5AQ?WrsDhG^Y zRb;8g{d2Kt!h}X?^_}0C(u#z@rKhl)&{IRpKf_U2j?omsFSh~`P+)UQ)E0#{y}N;q z5g>fgNpLSxeSneaN=U^%FVMLJAsdpUsD+Y1NJ+@1`ap4uf|VW3sPk3Ju)AB?}^3~zmXZEf26r|cXNiiIXy!ta_tfgCc zxcP$3=u8e~l=z!hb-QIL3kpF=!8KUdlGB$?mfzUs>;v9uHU6Tq(QV}t5!lR#tf_et zX~d~0(%7N>K}vYnnIdntH&!ou!o@>!@0uoGhj+N%}N0TT@(+>0D zK#5Lz3N5FV=6a8(`%+}Q6i##PMxx;b(0Yt!&%^7ZXAQ_nb=|U7w(s2_- zwD@g4vqKc)um(wROqo5a7kTB*NFAsZ-L{;`;(~VQ5fj1x05xwKvOBGWUQ0>A8)l~RhF3) z&%2i8K&1|A!lBloQpZaA6&`@uOX8y{lToR{PHO=JCZLiqC`DFbfS@^j#tNiaQd}h|27XV)YV!rfE6hOS8$?kSU|Xp^a>ABR z(xaK99eo~bdQ@YjIwB<6Vn!`nWRQ|D36s={0XISAJFvtc4YFcpq}CnUpq7+ULb>;) z&D^=U#*oTNx_2kui=mNPuuVecDMCx?0l;t~CabpvS61$TkT##@5l-&yy!D?vB&skb zK5845Qr)_PumXtosIl?Tjh4{cK?+GwNZh4IC#_3iap&EjxT1!EJF;?-)7qfa?-6h2 zgdm-u3{VzVgsBZo@5+Z&edWn1pNzh`#zh~^qoa?YjI>!<7iSul)}09p0&$=4?II>WptZuZ*jDBdr2l7&rmC( zUg~DRi)Ivu?a;lM_wsA7X++tDj+M8 zqgEdm7)nQ!QnM)n5f_nT_Ug@9W5QqvU1fFT# z>*s3u1okwQl$)!+r|9T=(8=;-rAX*L7->Sql!U_4t`sB~7$Tot>F+v?%rZ+U^{HE6 z8SnR{{uQ{kM%M0{3QAI>sbpm&@P5@|y1r9e0c{FE8$yy~EDjAlB}Q|UmQ5ieRu8h` zLROOE#DG-;?Lk|%v|cxiZMAZ%fJkoT0EEa^7TSQw zB_x#hDXSIr$*nolr)y#ImsFFGg(yk= z6(YRuS7vNGEmpGQkGAnXXVsn!KhT>CVL|n7A+;$a0h+9Njuba?6|r-*UZ1P&PxQx@ z*7<-OM%g{j6qVT7bWeY3meswu3fufZPbb=uo1wg+0x(Cbss8{qY1Bapc_TXn^Vo1t z%}`uXYUn9oq$yj7{{U?N0BUUFg^m)^ZWX1zS8mzD z{7m>7#nU^;1w^FGN%pF;wM&b4RNIRuZ$p~1c>37?0IWYCp?kL^#wworg~MPi9<&Lb z^@))+CRLLqcI_^i8$yqC#R#{G0p6HOj8s8M2LsdFv8hVX6Y7#Z>Z_*8SrwyXKk7(N z0P;Ff&D?;Yy&#kP)0zRaAxR}f$?61D!IJ_6#~GsdlSNJ}Zo}cgNFgR`WV`A09mlby zjJB_-Ngyb9srM1;1`Sns(qie8zjL%Gg+NbnL4IUnCIOU_ShW+C9m9Y-P|5^=1c;MM zh^bZSQ9Ov4>?z!mi4j5e>I4#1HIyqZ zq#&3AvZ}dkBkwZgjF_ktyqwmHa-#-)tLR4YC^AiKNQ#In-n=V{ zdP(UtsVC?zNh^Jw!$5lTP_#wvq=MUzdiM6Fs!%u-)p}`a*)Z8kW0=Jvb=0)!Oo~=a ze~8#KcNP$c~>C4Jo-F#3@8c#%Skm!p_tT&mz4jZJo<>60dq&N%l4_p33{oEe?=y zDiX+TTh9PfV%T<6K%p*^utESPc&c(#Ta3iysc)@LEnbccaavv8sfbL*GI=z`OkkuB zt0p?oFEEvrIHCbmf;&YDOnPawB}+@bbQe|V2LPPW;eyF71nhDQ9<}Rm*aSg9Jke?; zR<|k{4f>Kf#W^KMN&OXzyRGTnbRlHzB`MB0#VgibOO~i8R^c#5P<`p8_y|CH3ez$` z9D_@56>4qXT&cCHNo`3GOmZ_pZ=%Ak%1z^-I<%i(Rp+gHr`@#G&{TRuYEUuSy|`#7 zERs^5gWiX+w^rADz@j&SB;e7-sya{F3e}u)@iw;Yq#dc;KsY4Qdz-{sI;(aFaYEGrAl?|v)0!O@iRcKq63vK}k4rCsttU`r1W8Vv*dWNX;?=5Qx!BUKw z3B-Q2+I%mnCEIkdex2wi(i8_zdggu^(-UuN<*ril%)6P9{iwUGU|Zj?Ez%oq-_bM3 z~`Q*O4<64(HdOqtieF=&thQWP;iH40cu zthS`9)K)^I@m`)8BboAvcNN1Q5k=z!vR18%Bamy&DVuwB`OhR26FXIrH0M}T9$Fbl zDG}-<{Z&}iZbM0FyMIXs)HtGli)1A7MvMOdhtw`#eZ-{wAtVAKtnFH{cWk1c;jOWd zYK`F6)YEEIhSW&OF;_?1bqytIcqh!@Zi7#QxY$8xE3wkS1t!S|v*J9hUa0LJ0Jc^HAS5T#ESi#u`<^H2o6m%D0yka_;a5 z9>c9ywQmz$uVYDgrUU^$NU61p@alG@K3hmIcB;8giab!8CrcJeD^Uj~4OP#TVpN$5 zIHP$cfV_RKrtycEV*Xub7U@BSr!W*d?Zo>Ur{3QG0I8zKRV}tIJntsprvXlG3rg{T zPpD6Lpzd5R>JBS(A!LOf;X<|3PvzQGv&8vpcp*T>Qc?gT;Xx*{@@9EEW(HY&lW*6e zL@;gKc?l%}m6bO#laE_R(qewrMLWxpU#Pfr}sfq!!d!k01`qh;w)k&UXu&SEdF9ouxVZ%c4bXW5rE6@^ z{L@Y;8Tv_kAa0GI2&*oer+UJa2tM=% zkkC7lWRw}t6IU%AlHdVDDKT1pJ~bkSe4J4|wYgvp44$-mjwrUGji7;BziSAQf-1YH zYEEhK1Sm)f6{O;zjE9qx*hTHs0HZsy2~QNB-%wkZsUbk5q^4ql)UJcBZQf7@cPPZo zC9-$&PCWQ=OIF}U?mI;yQbPIEif51R*5;!N3Q+<*Bc)bbu`5zaO17SZn5&vpPY8ewH<6`iF7YmZ0n9X>m1_(a0f}d((s4D`#7Ze48j4|9<1!55 zqIqP29-s$apA^R7Fd*gx8dq?iFpy*j=|R{|tfl;R z6(4^n?%>pk5GoWL)SI*IUi{le{VFF6W@~b;)uAK#xUL9*tW+*#CUHl3DOkmNEFg%J zTtJm_Mk_$tm5O|=8+arEKNTgukfg8FKWgRxp4U$<9P>2x%STIaLIpCmuu2H@kJ6d8 zfB=9XP=#kU{9Lqi{{W*elWq?3h!A6}Ra5X|PPfsRx-D&X=2E5lCur#>o`$;TitU@b z(k>c%AxS?R*Tz?0idu)^{{W9Jt=n`9N0YTdV{kA?;*pz^rxDO;UoU6ei_~=c>nS%0 zQo!PWXs&&(X_po+s_|`~$|wjUfO^-=cVCNdv#HzO+g-X?D-IS*pTiN!BeZm{r?e~2 z5qSIX{WsT$wXtIHpElasO5E8VlB3dMW2B0YkxCRTE*U5W+Uz=w>M__J)i0Wr%vBoK zRB@M9)zhP56cHo2>F-uVT_KdEB<@;(DeqZXQL6x(l1Ky{sp@qG3kLR(f2{wKJqzO!cT)iRKkHr^1RWRXtp$`~p16&0s)^WUfI zKwh+T^f%9d12 z`wFV-Pi*til&Qr8obZ{GA2er&A0dm2mlga%u#}V3lfb3^C1?b=S}9D#B>w;|37TqK zpj$0kMe>}mZ7Fb_jqfCw0X-?LrlwwO5V$;)8mDRQsA;aHM{BQJBp>@gGyBz>!vph{ z@4F=_e#sI4yh;MCB z8cm~)KH5_|)ZW?0LE5=bhxMo3DP{Gk#s2`bsENfly1uwd+ilg534oArD?$0tbkv3GE=sB>$Os4>JC zs!P|8sbRZ^$L5l$JBd4!Kc~G>mz>(D9g{vBZAo@A*E~aVTT^W~;u~7BLBW$1LB9(Z zO?vko^F6()-*CyhwQD>Lt~Sh<11D)xQj~x@;%Y9zzebl$Zm^Roaj;d+ z2<=wQKls`!E8I`e00ZBT-m4F-Y%<^}2~wscV~%Opgq{v)p(J`5Pym0otksNE^aQ@c zZo+Of%|V5g1-9^#u0TC0LnsApf$X2snp+gzTxvx{Docw7a{!78&9JvwNe~i}Vn0fU z7ugAgf)LZFB$S2ndRK|JBH2=Rbu-?IC`mh`jHr9}t+bTx4i$hzo|H?VWZ$DpQMn33 zfcjx+BipCVS=ePa3A9^akcANgGfJ$iO5Ia=1oh%7?cx0$ep*uMNkAY76>lCFH=@2w ziYsQCHM!peK~a1k=0Ag!r4i^s0oY}eb3w3QM zJBS7k70&!f&BF^(M#UvqgV)-+t>{5)=O7yAo+I-Rp)G}^{?zQLRT=KiU8vkV<8CtG z+E9W(<|?Yrr*g~BHno){KTbLJ6>-+sch4@i6s-2dcB*~r1-q9N!ncAXjw9MD6DpTt z<#rIxqNSi9sYy^Usx>Y&zMH+z^rSX{QS_85PC?BuZ+>@rLGM`El$+TxkZ?jvo6?8} zfNDrnYa2<9^%7o7X@wF}M@op^g!-qT>s7c6Q>GNPt7?k(Wb!BaP)`KAxGaGNCL=ZL zw_$Qbi9P5yoz6~Y6)}8EJd-v^4XBKy@r=}uFLFR6!c6fLFkGv8$O3CuT@xshdqoVV zWog%BgbZU98;paAgV2BH6f&;^K$AVNDj&~oFbscsLUzg$Y3S1Br3EN*Pij{1C{L>d zQ!?Rr9$V4;#}$<2B#SE&XWjMg@0##t zq)I>%NUv>W#Emx--ms*R)}?@W6Ghv#z5z`@WLC{6nF5^Jv`8C-A8}q=+JHm~V%tF? zy2ZZFT3}{>rAg%hzyh~GfnF5>TnQSR>)N6_l459}pg}z-H?A5XP@oEV%|()y_E-E@ zs@mD<&#L{v(?KRr5%FIV__M)!uZ*?J)ad$qw~kskzx_jS0+YzacQ3}j6s$Vdon)zA z`6(Hk@mH_IYc$yCtS;>VH!DoIpqM6e(yO^HRp@sfaijC}XgbRppwq4_?yX--n^eoK zsRY64--`P?!Jmoxu7Tm}O+Q()TS`Mgwz0x^1Nzms#n!f3&b_9fTQ=jYt9snpB_p)e zJMm{+wbLxxX#py^iNKf@Uo(o9gTomsZIw+=P;I{xesgM-YNQteH%JD04wXKO;&tjO zQ;Bq~^{q%zCIHXUJ_T}ZcT`@ZZkH6{wp;EHwWT{qK;X&XQY|~hEBy01PMoDO(A*NP zs$b8&1{_~5vka{>WU5H<)HrnF;!|^`j%8b=9Jvu?O5EbDD6n^Rhn&%f+V_nRH;0g zUQ`0k+^3=KQNHrs2}5KJ+|4SyzJ`<&xh+n_n99D?mbP4vLBi}haRMLr^`~kF_I{7fZejznA^CRDe7qBpkBZkd&>n0R>oNM zj7*+?tx0~5%r=Fn;G{1j+Lnj*F>+p58}MLoI#90PsM`4r0$hRtDx3k+^{2)nTMM?m z^Dc^bE+=k!3=jFPf#8Ic3!8Ta-KIhB(th>Ld_D`;tfgcDwl@)x9V?~j6J=uSuK*N* z0K{$rx$8u)wq>Q_#w<6QB&CxbqoO{kgecALCka>^li0}acPhaF+9@-H2(lE%mhk;W=%tO z$@D6E*$%0)l3)}3w8u(X{{S$CT-*qf0(byoPwiWlHmoEkg=AEETK3;wsntkjBqKNp z8K%WprXQ-cy{)PtU%{Mong0M3W`^ivM|g-zTsQzn@6Q#ZPH)n!%G8xO>6juVBZ;Zm zLRcy!`ce+yK!Hw*;_MgE;v6>C3sIfUC^k}g8RU2EMYpxPlxvNhB>sQdB_OO|;tud1OEe;EM9X%}WW~Av|PrR&6@O zvm5e&bDjlQ$B&s_c{r;_nqHVx;88|k3h25`t7$+bB1{ryk=Qnd)d@)Xt2LX60B{9u zV8ozgX2|E*_RT#r6veheS0geheVeJ`X{%>&1Xhx!Q5I^9A1suNkvvx}@hi?YwImV< zk_oQGcGmWY>lMp9TV#~1seETNjPccxOy)X{y|vwrwpARS1thp_Hwjy;s3lSK&p$Nh zQ?o+KhnMTTJk}OQ{jofyVu#lAZzxDe8T96(I+vIAsFftfM->h0 zs&T(bBX48RwIO=vVvbJYM3O5$aJDK_qSOAW6p~~DJ+o0R=WztgcBymSLV6E3 zCQqyMpw#yAL=hRNLdw3CPu?hXx>8dTL7dMOUytMr<)DJ$bAn1wb5f-c0Z=F08eCtA zAmp5rQa1RRP?_v#s)~00h6zzE&j~U}>?$$_NRSQ$b@`~jGw3a)Nf_%;xp9w7raBs6 z>~q>YekEKO{c9*E-vn{H(Mu1*^tW+vNZJt`WA!yt{8#)vvDWUI z)tz~5+%l4tB$-N+GZ9>aQv5`J!cV20D&u2mGqfH^>s7MN;%SXgk#i(AwDGS#+$rKlGqd7BP#-j|9G zCPbfMXiXbV>B|iiF(`_G9 zxm%ZyUED4UL@6N1-8cYu_oB(+Md&F<{91Jj!j@c1R_HS8qm_FPYPIRQ z%U8^YnNaf8AtWAa<{KXpE6i1?mgYj`Sy4I3?T#wzruebzG^KucZJZ^h$5f7fK5GZa z@GEH4!{%~y&g|+Izew6Tj_DTF*NDrhQT0(d1{q_d+=(!u{1GFGsc<r>h`qPbFFs7j3RXj|~10kQ{}eK^6SD7zY@nitqO;~UcO+U3eODGDC= zKiY*4EgRB7QXEMsQ8NG!^F*yBPAX)&h?CEwpX)UOr*78Ar9%oCD)x##EfZn~8X?&Y zfa&LNZuB#wZN0-}9l%HgMrN#PWIEcVBrGLLNGIta(o2~yIH#nmXavVfIu!o^5~F;T zI#z~>B#h1^b(#&70_u+K&^Dw2$69NA5TNGRDkf*FP_`{^aU>2*0gV3uinA0Y%g}#^ zm^<7n5a*Z`%$~-tmsdvG+&19C(dkuEwAa&VsGtlLD`JHA_p6VHnhDc^KCQ`=k^LxI zvSTGwRPV0dd&me^ndK;M9Y%Ul`fbR&zYVErTWKSC1k9eXN-dY?JLQqi^#s~n${LR?Dkp%W{{Y2WnsG>Z=2W!#YD%Fh zQ8P6yZ$zmkj(u{<+ig!K6+c>Y={@mL+c>wIgs39nuuQDR>aaT7E`m`QrpocB3u{);x$A;Y5Guy~xDN%TFqM5Wa;ymv z991VvTE)eeV~$f>J^0{wdD10vKT+Z!26RB=qm`N>GzG4!i3Oh~xRGu}lUuVZ;`YRsx#0mgA%d zt-RH`rp`fBsd3t)uv2?nw+iY>`82}ajgJsSl>j7rbfzFpwCh5qTq-3bp1tO+T1K3e zEoUfB!H!8a4w0tvHsH4)UIYyv(HAU`lDBDzEMm zvXzrFfM`p%(>tL#>(tQKw<&rYLZJ1alJ3$s8O|!B(L!yElD5^tppU=rQx}wyHswDw zq1OXSBZJUZqUU;&kfM4;B|c09y_PneCJCvUb~hj2wJS=>l8~ACso8N!Bq}(^HC9R8 zmM=&#PLNavM*}?2j<{7b$T^zT$D|N4Xk|Kb2!j~@^q8jDGIWW2!v!-W@tXB7>;#@T zGeNE2NhHT=5&OslD+vmZaaJQ2AsZUGdJu?{&tpQnf4AtF>jsoPdZK=!Peph3s8r}u zvG=PB9U@|#ja|Kv0+MH?Wg~I!F?4>CHP7Ce+KHu&C z^MB&kh;960fS0bkuO(81p6Wf(eh{_=z><{h z5EIzqzHc^H-X_&t)HPQM-B~J?8UFymRuQ5wr<__qStsg@%`!#1RtPFoZj29TuVv!@ z01=DFR?H0kZ1c*WLN^g@r&>0EkV<}-5J{@usdT57LYAVCI*usqM&)-`3RGUBO1N1%{k-36}M>QV4s?BmkQL9TsTyis^yfWXKb12P0RjTLP*IN zF+qa3Qwn-vsJ$q4K3Abm7%Nsu`0Y$}Tjg5YG#qfc>!WgzS0E06(mg#%^HS@JfDUo> zNAjoq)OWXkn{m}B9ISQe&{bI_IatLekV^Htjjio5oDcx`1KOPF+V#bnOO3dt+zb## z;yD;RVKBN|6{T{{5@wHC-(!LIERiDpG+t#y<2-p0jk;@n*8#!33#T_ax3uV`cFa zx-_x*ZaZhy8g1fG(}Z&yrXvEjwRAahTu=jkRH4A_BDkiB_=f5&e1)&`D{RSD-LjA} zbM~t~iQ`6fWcgORfll4-IQRFfuBmkEmW_$r+$6{U0FbV8c<-L`jUijk@NvNQt1`6%ai*?rS4n7y@f)%B?;b)t|{*p5fVb1h|RONzF31C6C@uMxNpr2JKq z&sr8%%m)F4r~@3PI)PN*7TtN5t92^`D8dv!p`*tZc1fgx>kg#TZpRlVoyL`49P;BiF z1GAy1Yv?u6T)5o*=v0MlN2jJqrMLF0V&8Ln(trsd41!NIYKkE$DN`VV2Hr#4_axaIlY+}kt5!e zYHk63kcAYuQPCiU51PA=NJd3rc6Y(h(=HEL1_WY7NZh4PiI$h@j>@I2`A+OumXw1S z>S?5u|R+^uZw3Qq{$9h!bYDgiVk|*Mq-rc6bwOw5} z8DYGu{0{YI5=(5A4u6eyb)7E#tAG{fBw}EgkDk=c9wXB&t=+kH?FEJ)xU?CM(!MtF zm*Tbc@OH~bTI}r9VM+(ozIxS!n4e&!l#Wzu8@lQ$|o*o_gEP?o1w(x8^MW0F6<1`G5LTABo_!Nsxh;*3D5M@p>qqKjG_{8t-L!I2IjZg5$WbXNJGiWzm&w`l zMyP-UhYEqgJ^8Iv>H>ymymY7@E(8T{+zIM>bz#r%Gw3 z>1AXs!H$6B(ycAB`^W?mp17;En=2(DDLYe%Bbr88{7Xcrrh49%fRJOPed|}ON(MNm zNl6M41QQtT?^yd#g9(sMYc=I_dkUi+BB6501xXh^OL8>`cF*UCqpTS0|H$5-D+fAc?(uYbn$w>k$OB_{M30W8_ zi8&SFbDvgwRO(LD@Hpg}*T~%kJxqMl`VB55D+MDtk;P?h;vkIf5yfRXJq=3F(j>VY zn5o-#atu<`EU4y;WR8`gJsZ*JXw#4fUwWKA2L^#;5<7~KO_TJ@)qSw_TM7Kko@4J@ z1(ggFP%&!a%#f4wO&PNycNzOs#~hg);*c7GfF#L`@l^d<#^YMOOB)tgNy#F*IxSNW zVx>i+Xfv>VsjCb2Dda_3$3jy~rKBY&ND+$Vzm1yKzu@~TYn>&sZ5&?Lol0c4j2~~B z`T!tw!1$$__K9h&**j@`%Wp91cBv{&T=Bj(wt#u+ByB|XO`iH;0dygGrQBsED{r!Kl&*hx+I_gW;-}*Vz zEC!hUy(f8IvLa;nrcM&2DM7A4jshxtrWEH{t^gO#1Vr-q^SgsgS|?dqNol@ z_ns*ZSJ-rdZZ1LyWjWdgdzu;76!T#!Nm6GZ3|2r|q!N3{tyYoKBpOqKVR9w2ebio| zDU^XA4j@ET^T7oTZ?PxYvdv|YG*K`g7(fJXwUTF;5t zV!@`|aO@@$fPGFy38(AD`?NOtl(hXTC`<&1{p-v;5^Iw^D$N+^=vLPf>(5Tq1&A@o ziWSt2?x{+YgEPd*rPkLi*y$;8OA12NxH1VOQrpd8NnuHONo;NbDp8M_r7SIm@}z(~ zMR=z5Cy+=UT1LXIiHucW^QB>7vV6sTP63hLDqj-nqW=I>x^bbk5R{-3K9Gl_- zV@dT*WuMIkL<9vuAml|d7`6oR&}%m;1?S2BCIV-n??<_)78(g`s3&OLL4)uOSKPdB zV)v}3ev$7{++HL&%9eKS0Wy2_ierbM7DW_UwAHD*wXk3XDD4UV0CzQFOTn;*lB~?n zziOtna9djs3=+7fHyV*MshAXO519?G2kMQ)ydD`i2J? z>}$py-W3T2Ai}y&_@P+1C1`0FAk14DZLIE}p% zwypcNN`~XMc>e&&rkZ`wa@n>WAc6@@al5gqotC1f1;Vy~QWBH6VKdgZYVkogjii(* zMqBCs0L@B^PS9MfY-_0{04+d-r2y{$XB7s4s~b0y>Kj1?D;)<=errbcA%CYEhe2xP zleNrZJI88H9c?Zl#*$lcQUO4es2;r2l$3iRN0YO<=)^bztydvlv;s%p?L+I9{#D}_ z4ku$|dPq1ERQ~`6>n6`-tB!DCl>>k~8oDm+okrbhGrc~NiJ9(d%r=EhIJ;m?tyb$P zQi701=pAXRH_apo+Jo4hekcNl+GR<2gs5OE`%=e?h24pRxWY=rMLEHwL}`q*?-8}7 zGF5ZA0Ob%Tb#YG@c%JW8UdyYHZAm*4nHcx?tH0t6-}+9N!z)s*9d>t)gW|Z(r+lw5 zNg(>B3P>We{E0i_neIP_@!<@AESA7-o&rHLi4-2`Jdy8M^3XoD?d%B2_MjeF+&tsu zq#PwDBz)Cuc&O9zJhaw}cZGD@O-AL#U%rGe(tSj#2Q}FI9<3p4DXTEsEUOLX3cb%- z`NG((^&zAks9EEiQ=iF#@gG-N>!?@L*~6kwXW&}2Cf`&Cmc;_ zOZeDLx6Jvpq`rsFrRGvi(vZtXlB1G7C_7urE4z;^ozBN9A^@OX@a^k}4{A^OiY7`5 zvr<^w{!%tJ1Ykyf^;a%04+I)pkQFA&=7gUqz)}E7;;xoR3Lu@n@OZ9$r{9=CDO3U2 zQ|nvPx__2>oY#x;l%(51sHNF;LO}^n88s8Ek`*F7iKQ)S!6PRfk6MwiY z8z}x#>h&<6rDg!36vaDT-HuG`;uS>C6SwO?2U~+5O-OXiXawd?s zQqx$}?c8SVq`cFEYLcXa6gyYVpNOCI;0Z+j3q}i2c>0YTfyaJ@nv|~ zUnUHGLj2LiZ9GfjKLft@nc`hJ*DY3(m+bD^lqhj2oCEPowT}d79w}kVufzM(Z>UVU zrQE9-0(L0>07}Zr_4y>?qb2@bm{|V+@;4XhI)48E(aJBZu5|mA+h5zcZ*bb7Xjp?i zsaxZDlYnGmva+dUS8RM6YK>Y21QZUm)s?A9+Ehkh;{)chvV#tj4t)|bRFXX>Y02zr zMW`r!%-)9Dlc0CB4(m8`h+<*|4a+p?8<5+Xpz&&_3JR?7&uj>O%Cxqkkjy*X~W zwFIS0Q1(40m_6bJTxz!LT7M4QxD12!Z~-x&f4ya8WKI!!G$T1h=y>0W*>d5An^<*= zJO>H%$tQV_?@9IjeQUmM*}?)m(=cSHV{K(+R#1;2*)mFj65`ou_t%#eLPCAIRPMYJ zW~PF+q=`@mAR5ZbmXJ2Znuf5ABE^>3R_6+K1Gq{-GAfSs@=D93{{RqkyT5VXva*|s zD#T`!v&U}~y~A-OUrG0^wHwl;8ssqf_m0gRH&;v zk35iZCD=AZo8ws(ST9261ZaE4d&f|~tjBJY>sv2tnKy+RD?}T!IBBy17~{t(*!XS{v0pYbz)skm$A-r0WX=sShL<2Hm9n zRBP-uN9HUB#3;#ccTaEeSy@xi=)Id+->D9#WyZ$|kd@>2t4+U(?m$v))|5H`fUJdg zipt7t2B@aCZpY%2c7zR9&&x6E*d%=Cy*ahe>&se`?Cgmy%8K zG%@&?oHJhfG3PrR!}`g#qx8@#q@Rm0KlKa2R{%zMnBY8bNw3G zGWz3aQo1E4_ciqw{{Yg*;iA^3PL8E1Su*j-HI`_)6lABL`JOf9`TNo=5STSx}7vYC;~C+y4x2t%ZC9dky% zVdb@RYU^ZRZV(UNs#n%-*`3G+LCEL#tgNg&(Miz_+5~S^EwA{g_W1YsrtIpd94bEj zd)8J~R%k_l`$2GT7iisCl!^Ip5YK$*e%W4l(hm5b4jjUDwzkdtgNdT z;;fXcTH@qLoSKA_1k93aD=O@I8x?Hz3V{cuM)SvXZ^yM|Wj-WQ0(tUIR58=7Wo2bp INl^#?*;%B4n*aa+ diff --git a/src/deepsparse/v2/utils/state.py b/src/deepsparse/v2/utils/state.py index ee2d71ae0d..b54b890acf 100644 --- a/src/deepsparse/v2/utils/state.py +++ b/src/deepsparse/v2/utils/state.py @@ -20,6 +20,11 @@ class State(ABC): + """ + Abstract class to store pipeline-level and inference-level state variables which + are generated by some Operator, and required by some other Operator. + """ + def __init__(self): self._current_state = None @@ -28,16 +33,23 @@ def current_state(self): return self._current_state -# Created during pipeline initialization; only read access class PipelineState(State): + """ + Created during pipeline initialization. Pipeline state values are ready-only + duirng inference. + """ + def create_state(self, new_state: dict): if self._current_state: raise ValueError("State creation is only allowed during initialization.") self._current_state = new_state -# Should be created during each inference run, similar to the context class InferenceState(State): + """ + Inference state, created during every inference run. + """ + def create_state(self, new_state: dict): if self._current_state: warnings.warn("Current state already exists, overriding.") From 809cfc163c0a9d40c3414dcbec609d1b4d3125a6 Mon Sep 17 00:00:00 2001 From: Dipika Sikka Date: Tue, 24 Oct 2023 21:13:40 -0400 Subject: [PATCH 09/12] Fix typo --- src/deepsparse/v2/text_generation/prep_for_single_engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/deepsparse/v2/text_generation/prep_for_single_engine.py b/src/deepsparse/v2/text_generation/prep_for_single_engine.py index a368d90097..6ffd2cc63e 100644 --- a/src/deepsparse/v2/text_generation/prep_for_single_engine.py +++ b/src/deepsparse/v2/text_generation/prep_for_single_engine.py @@ -39,7 +39,7 @@ def can_operate(self, inp: Any, context: Context, inference_state: InferenceStat kv_cache = inp.get("kv_cache") tokens = inp.get("tokens") # if 0 prompt tokens remain, can't operate (multi-token engine has already run) - if len(tokens) == kv_cache.total_num_processed_tokens == 0: + if len(tokens) == kv_cache.total_num_processed_tokens: return False # if number of prompt tokens left to process is >= self.prompt_sequnce_length From 6336d8e1b4f4fd6b37399629d790f0583b7949cc Mon Sep 17 00:00:00 2001 From: Dipika Sikka Date: Tue, 24 Oct 2023 22:23:10 -0400 Subject: [PATCH 10/12] add todo for split/join --- src/deepsparse/v2/text_generation/process_inputs.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/deepsparse/v2/text_generation/process_inputs.py b/src/deepsparse/v2/text_generation/process_inputs.py index f4d5855ad7..588d011254 100644 --- a/src/deepsparse/v2/text_generation/process_inputs.py +++ b/src/deepsparse/v2/text_generation/process_inputs.py @@ -121,5 +121,8 @@ def run( frequency_penalty=generation_config.repetition_penalty, ) + # TODO: move this step to prep_for_prefill and add attention mask to the output + # this will allow us to split/join more easily when processing multiple prompts + # in parallel tokens = input_ids[attention_mask.nonzero()].tolist() return {"tokens": tokens}, inference_state_update From 3f2193d3d043c1f7f84f898006557f92c76613a6 Mon Sep 17 00:00:00 2001 From: Dipika Sikka Date: Wed, 1 Nov 2023 12:31:58 -0400 Subject: [PATCH 11/12] remove context, clean-up args, remove prefill_preprocess_operaator --- .../v2/operators/engine_operator.py | 8 +- src/deepsparse/v2/operators/operator.py | 26 +++++-- src/deepsparse/v2/pipeline.py | 77 +++++++++++-------- src/deepsparse/v2/routers/router.py | 27 ++++--- src/deepsparse/v2/schedulers/scheduler.py | 24 ++++-- .../v2/schedulers/scheduler_group.py | 37 ++++++--- src/deepsparse/v2/text_generation/__init__.py | 4 - .../autoregressive_preprocess_operator.py | 52 ++++++------- .../v2/text_generation/compile_logits.py | 24 ++---- .../v2/text_generation/kv_cache_operator.py | 23 ++---- .../multi_engine_prefill_operator.py | 26 +++---- .../v2/text_generation/nl_engine_operator.py | 53 ++++++------- src/deepsparse/v2/text_generation/pipeline.py | 24 +++--- .../prefill_preprocess_operator.py | 21 ----- .../v2/text_generation/prep_for_prefill.py | 40 +++++----- .../text_generation/prep_for_single_engine.py | 66 ---------------- .../v2/text_generation/process_inputs.py | 11 +-- src/deepsparse/v2/utils/__init__.py | 2 +- tests/deepsparse/v2/test_basic_pipeline.py | 4 +- 19 files changed, 248 insertions(+), 301 deletions(-) delete mode 100644 src/deepsparse/v2/text_generation/prefill_preprocess_operator.py delete mode 100644 src/deepsparse/v2/text_generation/prep_for_single_engine.py diff --git a/src/deepsparse/v2/operators/engine_operator.py b/src/deepsparse/v2/operators/engine_operator.py index cbc65009ba..b7d920a686 100644 --- a/src/deepsparse/v2/operators/engine_operator.py +++ b/src/deepsparse/v2/operators/engine_operator.py @@ -59,7 +59,7 @@ def __init__( num_streams: int = None, scheduler: Scheduler = None, input_shapes: List[List[int]] = None, - engine_context: Optional[Context] = None, + engine_context: Optional[EngineContext] = None, engine_kwargs: Dict = None, ): self.model_path = model_to_path(model_path) @@ -114,12 +114,12 @@ def create_engine( if engine_type == DEEPSPARSE_ENGINE: if self.engine_context is not None and isinstance( - self.engine_context, Context + self.engine_context, EngineContext ): engine_args.pop("num_cores", None) engine_args.pop("scheduler", None) engine_args.pop("num_streams", None) - engine_args["context"] = self.engien_context + engine_args["context"] = self.engine_context return MultiModelEngine( model=onnx_file_path, **engine_args, @@ -135,7 +135,7 @@ def create_engine( f"{SUPPORTED_PIPELINE_ENGINES}" ) - def run(self, inp: EngineOperatorInputs) -> Dict: + def run(self, inp: EngineOperatorInputs, **kwargs) -> Dict: if inp.engine: # run with custom engine, do not split/join since custom engine # may run at any batch size, returning here as code below has a diff --git a/src/deepsparse/v2/operators/operator.py b/src/deepsparse/v2/operators/operator.py index 62591187e0..c7705743f7 100644 --- a/src/deepsparse/v2/operators/operator.py +++ b/src/deepsparse/v2/operators/operator.py @@ -17,6 +17,8 @@ from pydantic import BaseModel +from deepsparse.v2.utils import InferenceState, PipelineState + __all__ = ["Operator"] @@ -54,6 +56,8 @@ def has_output_schema(cls) -> bool: def __call__( self, *args, + inference_state: InferenceState, + pipeline_state: PipelineState, **kwargs, ) -> Any: """ @@ -81,10 +85,18 @@ def __call__( "in the form of a dictionary or an instance of the input_schema" "object" ) - - run_output = self.run(inference_input) + run_output = self.run( + inference_input, + inference_state=inference_state, + pipeline_state=pipeline_state, + ) else: - run_output = self.run(*args, **kwargs) + run_output = self.run( + *args, + inference_state=inference_state, + pipeline_state=pipeline_state, + **kwargs, + ) if self.has_output_schema(): return self.output_schema(**run_output) @@ -99,13 +111,11 @@ def run(self, *args, **kwargs) -> Any: """ raise NotImplementedError - def can_operate( - self, inp: Any, context: Context, inference_state: InferenceState - ) -> bool: + def can_operate(self, inp: Any) -> bool: """ - Whether or not the given operator can run, based on input, context, or state + Whether or not the given operator can run, based on input """ - raise NotImplementedError + return True def expand_inputs(self, **kwargs): """ diff --git a/src/deepsparse/v2/pipeline.py b/src/deepsparse/v2/pipeline.py index b836dc71ed..ee5f796ba7 100644 --- a/src/deepsparse/v2/pipeline.py +++ b/src/deepsparse/v2/pipeline.py @@ -18,7 +18,7 @@ from deepsparse.v2.operators import Operator from deepsparse.v2.routers import Router from deepsparse.v2.schedulers import OperatorScheduler, SchedulerGroup -from deepsparse.v2.utils import Context +from deepsparse.v2.utils import InferenceState, PipelineState __all__ = ["Pipeline"] @@ -40,27 +40,34 @@ class Pipeline(Operator): :param schedulers: A list of schedulers to run operators. """ - + def __init__( self, ops: Union[Dict[str, Operator], List[Operator]], router: Router, schedulers: List[OperatorScheduler], + pipeline_state: PipelineState = None, ): self.ops = ops self.router = router self.schedulers = schedulers + self.pipeline_state = pipeline_state self.validate() # SchedulerGroup handles running all schedulers in order of priority self._scheduler_group = SchedulerGroup(self.schedulers) - def run(self, *args, **kwargs): + def run( + self, + *args, + inference_state: InferenceState, + pipeline_state: PipelineState, + **kwargs, + ): """ - Run through the operators using the provided router and scheduler. Update the - context to reflect each step of the router. The input to a given operator is the - output of the previous operator. + Run through the operators using the provided router and scheduler. + The input to a given operator is the output of the previous operator. :param inp: input to the operator. expected to be of any type that is expected by the operator. @@ -68,43 +75,41 @@ def run(self, *args, **kwargs): """ next_step = self.router.START_ROUTE operator_output = None + while next_step != self.router.END_ROUTE: # Either a dictionary key or valid index operator = self.ops[next_step] if next_step == self.router.START_ROUTE: output_future = self._scheduler_group.submit( - *args, operator=operator, **kwargs + *args, + inference_state=inference_state, + operator=operator, + pipeline_state=pipeline_state, + **kwargs, ) else: if isinstance(operator_output, dict): output_future = self._scheduler_group.submit( - operator=operator, **operator_output + inference_state=inference_state, + operator=operator, + pipeline_state=pipeline_state, + **operator_output, ) else: output_future = self._scheduler_group.submit( - operator_output, operator=operator + operator_output, + inference_state=inference_state, + pipeline_state=pipeline_state, + operator=operator, ) - - # print("Current State", inference_state.current_state) - - """ - output_future = self._scheduler_group.submit( - operator=operator, - operator_input=inp, - context=context, - pipeline_state=self.pipeline_state, - inference_state=inference_state, - ) - """ - - # wait for future to resolve - operator_output, state_update = output_future.result() - inference_state.update_state(state_update) - - next_step = self.router.next( - next_step, self.ops, context, operator_output, inference_state - ) - inp = operator_output + + operator_output = output_future.result() + if isinstance(operator_output, tuple): + state_update = operator_output[-1] + operator_output = operator_output[0] + inference_state.update_state(state_update) + + next_step = self.router.next(next_step, self.ops, operator_output) return operator_output @@ -113,6 +118,18 @@ def __call__(self, *args, **kwargs): :return: output of the pipeline operators ran with the router for the given input """ + if kwargs.get("inference_state"): + inference_state = kwargs.pop("inference_state") + else: + inference_state = InferenceState() + inference_state.create_state({}) + + if "pipeline_state" in kwargs: + self.pipeline_state = kwargs.get("pipeline_state") + + kwargs["inference_state"] = inference_state + kwargs["pipeline_state"] = self.pipeline_state + return self.run(*args, **kwargs) def validate(self): diff --git a/src/deepsparse/v2/routers/router.py b/src/deepsparse/v2/routers/router.py index 2acf6c701e..a0a93e6096 100644 --- a/src/deepsparse/v2/routers/router.py +++ b/src/deepsparse/v2/routers/router.py @@ -15,15 +15,14 @@ import logging from abc import abstractmethod -from typing import Dict, List, Union +from typing import Any, Dict, List, Optional, Union from deepsparse.v2.operators import Operator -from deepsparse.v2.utils import Context, InferenceState _LOGGER = logging.getLogger(__name__) -__all__ = ["Router", "LinearRouter"] +__all__ = ["Router", "LinearRouter", "GraphRouter"] class Router: @@ -36,14 +35,22 @@ class Router: """ - def __init__(self, end_route: Union[str, int], start_route: Union[str, int]): + def __init__( + self, + end_route: Union[str, int], + start_route: Union[str, int], + route: Optional[Dict] = None, + ): self.START_ROUTE = start_route self.END_ROUTE = end_route self.route = route @abstractmethod def next( - self, past: Union[str, int], ops: Union[List[Operator], Dict[str, Operator]] + self, + past: Union[str, int], + ops: Optional[Union[List[Operator], Dict[str, Operator]]], + inp: Optional[Any], ) -> Union[str, int]: """ Determines the index or dictionary key for the next operator which should run. @@ -73,7 +80,9 @@ class LinearRouter(Router): def __init__(self, end_route: int, start_route: int = 0): super().__init__(end_route=end_route, start_route=start_route) - def next(self, past: int, ops: List[Operator]) -> int: + def next( + self, past: int, ops: Optional[List[Operator]] = None, inp: Optional[Any] = None + ) -> int: new_index = past + 1 if new_index < self.END_ROUTE: return new_index @@ -111,7 +120,7 @@ def validate(operators: List[Operator]) -> bool: return True -class TextGenerationRouter(Router): +class GraphRouter(Router): """ Router for a DAG. Expects graphs be presented in the form of a dictionary, where keys are the nodes of the graph and the values are the connected nodes. For @@ -126,9 +135,7 @@ def next( self, past: str, ops: Dict[str, Operator], - context: Context, inp: Any, - inference_state: InferenceState, ) -> int: node = past if isinstance(self.route[node], str): @@ -136,7 +143,7 @@ def next( else: for neighbour_node in self.route[node]: neighbour_node_op = ops[neighbour_node] - if neighbour_node_op.can_operate(inp, context, inference_state): + if neighbour_node_op.can_operate(inp): return neighbour_node raise ValueError("Cannot operate on any of the nodes") diff --git a/src/deepsparse/v2/schedulers/scheduler.py b/src/deepsparse/v2/schedulers/scheduler.py index a13fbeb040..78a58e3389 100644 --- a/src/deepsparse/v2/schedulers/scheduler.py +++ b/src/deepsparse/v2/schedulers/scheduler.py @@ -14,7 +14,6 @@ from concurrent.futures import Future, ThreadPoolExecutor -from typing import Any from deepsparse.v2.operators import Operator @@ -37,19 +36,30 @@ class OperatorScheduler: def __init__(self, max_workers: int = 1): self._threadpool = ThreadPoolExecutor(max_workers=max_workers) - def submit(self, *args, operator: Operator, **kwargs) -> Future: + def submit( + self, + *args, + operator: Operator, + **kwargs, + ) -> Future: """ :param operator: operator to run - :param operator_input: input schema to the operator - :param context: context of already run operators :return: future referencing the asynchronously run output of the operator """ - return self._threadpool.submit(operator, *args, **kwargs) + return self._threadpool.submit( + operator, + *args, + **kwargs, + ) - def can_process(self, *args, operator: Operator, **kwargs) -> bool: + def can_process( + self, + *args, + operator: Operator, + **kwargs, + ) -> bool: """ :param operator: operator to check - :param operator_input: operator_input to check :return: True if this Operator can process the given operator and input. Base OperatorScheduler always returns True """ diff --git a/src/deepsparse/v2/schedulers/scheduler_group.py b/src/deepsparse/v2/schedulers/scheduler_group.py index 75607504cb..40b5695f22 100644 --- a/src/deepsparse/v2/schedulers/scheduler_group.py +++ b/src/deepsparse/v2/schedulers/scheduler_group.py @@ -14,7 +14,7 @@ from concurrent.futures import Future -from typing import Any, List +from typing import List from deepsparse.v2.operators import Operator from deepsparse.v2.schedulers.scheduler import OperatorScheduler @@ -34,25 +34,44 @@ class SchedulerGroup(OperatorScheduler): def __init__(self, schedulers: List[OperatorScheduler]): self.schedulers = schedulers - def submit(self, *args, operator: Operator, **kwargs) -> Future: + def submit( + self, + *args, + operator: Operator, + **kwargs, + ) -> Future: """ :param operator: operator to run - :param operator_input: input schema to the operator - :param context: context of already run operators :return: future referencing the asynchronously run output of the operator """ for scheduler in self.schedulers: - if scheduler.can_process(*args, operator=operator, **kwargs): - return scheduler.submit(*args, operator=operator, **kwargs) + if scheduler.can_process( + *args, + operator=operator, + **kwargs, + ): + return scheduler.submit( + *args, + operator=operator, + **kwargs, + ) - def can_process(self, *args, operator: Operator, **kwargs) -> bool: + def can_process( + self, + *args, + operator: Operator, + **kwargs, + ) -> bool: """ :param operator: operator to check - :param operator_input: operator_input to check :return: True if this Operator can process the given operator and input. SchedulerGroup always returns True """ return any( - scheduler.can_process(*args, operator=operator, **kwargs) + scheduler.can_process( + *args, + operator=operator, + **kwargs, + ) for scheduler in self.schedulers ) diff --git a/src/deepsparse/v2/text_generation/__init__.py b/src/deepsparse/v2/text_generation/__init__.py index 2c6c351c17..37ac88d02f 100644 --- a/src/deepsparse/v2/text_generation/__init__.py +++ b/src/deepsparse/v2/text_generation/__init__.py @@ -1,5 +1,3 @@ -<<<<<<< HEAD -======= # Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,9 +18,7 @@ from .multi_engine_prefill_operator import * from .nl_engine_operator import * from .prep_for_prefill import * -from .prep_for_single_engine import * from .process_inputs import * from .pipeline import * # isort:skip ->>>>>>> updates func diff --git a/src/deepsparse/v2/text_generation/autoregressive_preprocess_operator.py b/src/deepsparse/v2/text_generation/autoregressive_preprocess_operator.py index 80329028fc..cfe7cb531b 100644 --- a/src/deepsparse/v2/text_generation/autoregressive_preprocess_operator.py +++ b/src/deepsparse/v2/text_generation/autoregressive_preprocess_operator.py @@ -12,15 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Optional +import logging +from typing import Any import numpy from deepsparse.transformers.utils.helpers import create_causal_mask from deepsparse.v2.operators import Operator -from deepsparse.v2.utils import Context, InferenceState, PipelineState +from deepsparse.v2.utils import PipelineState +_LOGGER = logging.getLogger(__name__) + __all__ = ["AutoRegressiveOperatorPreprocess"] @@ -33,40 +36,37 @@ def __init__(self, sequence_length: int, prompt_sequence_length: int): """ self.sequence_length = sequence_length self.prompt_sequence_length = prompt_sequence_length + self.set_capacity = False + + _LOGGER.warn( + "This operator requires the PipelineState to be set-up with the " + "onnx_input_names_no_cache attribute set from the NLEngineOperator." + ) - def can_operate(self, inp: Any, context: Context, inference_state: InferenceState): + def can_operate(self, inp: Any) -> bool: """ Can run this Operator if the number of tokens left to process is greater than - 0 but less than the self.promt_sequence_length. Also, thie Operator can only - run after PrepareforSingleEngine as it requires the kv_cache to be updated. + 0 but less than the self.prompt_sequence_length. """ tokens = inp.get("tokens") kv_cache = inp.get("kv_cache") - found = False - for c in context.stages_executed: - if c.operator.__class__.__name__ == "PrepareforSingleEngine": - found = True - remaining_tokens = len(tokens) - kv_cache.total_num_processed_tokens - if found and ( - remaining_tokens > 0 and remaining_tokens < self.prompt_sequence_length - ): + if remaining_tokens > 0 and remaining_tokens < self.prompt_sequence_length: return True return False - def run( - self, - inp: Any, - context: Optional[Context], - inference_state: InferenceState, - pipeline_state: PipelineState, - ): - kv_cache = inp.get("kv_cache") - tokens = inp.get("tokens") + def run(self, tokens: Any, kv_cache: Any, pipeline_state: PipelineState, **kwargs): + + if not self.set_capacity: + self.set_capacity = True + kv_cache.set_capacity(self.sequence_length - 1) num_total_processed_tokens = kv_cache.total_num_processed_tokens new_token = tokens[num_total_processed_tokens] + engine_input_names = pipeline_state.current_state.get( + "onnx_input_names_no_cache" + ) # padding is added to left, so attention mask is 1s from the # right up to the number of total tokens (prompt + generated) @@ -85,10 +85,8 @@ def run( causal_mask=causal_mask, positions=positions, ) - - engine_inputs = [ - engine_inputs_map[name] for name in engine_input_names - ] + + engine_inputs = [engine_inputs_map[name] for name in engine_input_names] onnx_input_names_no_cache = pipeline_state.current_state.get( "onnx_input_names_no_cache" @@ -99,4 +97,4 @@ def run( "engine_inputs": engine_inputs, "kv_cache": kv_cache, "tokens": tokens, - }, {} + } diff --git a/src/deepsparse/v2/text_generation/compile_logits.py b/src/deepsparse/v2/text_generation/compile_logits.py index a7131da105..55c87d791d 100644 --- a/src/deepsparse/v2/text_generation/compile_logits.py +++ b/src/deepsparse/v2/text_generation/compile_logits.py @@ -13,12 +13,8 @@ # limitations under the License. -from typing import Any, Optional - -import numpy as np - from deepsparse.v2.operators import Operator -from deepsparse.v2.utils import Context, InferenceState, PipelineState +from deepsparse.v2.utils import InferenceState __all__ = ["CompilePromptLogits"] @@ -31,21 +27,17 @@ class CompilePromptLogits(Operator): take prompt logits from each iteration run and update the inference state. """ - def run( - self, - inp: Any, - context: Optional[Context], - pipeline_state: PipelineState, - inference_state: InferenceState, - ): + def run(self, logits, inference_state: InferenceState, **kwargs): logit_type = "prompt_logits" - logits = inp.get("logits") if inference_state.current_state.get(logit_type) is not None: current_logits = inference_state.current_state.get(logit_type).copy() - current_logits = np.concatenate((current_logits, logits), axis=1) + current_logits.append(logits) else: - current_logits = logits + current_logits = [logits] state_update = {logit_type: current_logits} - return inp, state_update + return { + "kv_cache": kwargs.get("kv_cache"), + "tokens": kwargs.get("tokens"), + }, state_update diff --git a/src/deepsparse/v2/text_generation/kv_cache_operator.py b/src/deepsparse/v2/text_generation/kv_cache_operator.py index 182b7e62cd..0b232402b3 100644 --- a/src/deepsparse/v2/text_generation/kv_cache_operator.py +++ b/src/deepsparse/v2/text_generation/kv_cache_operator.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Optional +from typing import Any from pydantic import BaseModel, Field @@ -22,7 +22,6 @@ prepends_bos_token, ) from deepsparse.v2.operators import Operator -from deepsparse.v2.utils import Context, InferenceState, PipelineState __all__ = ["KVCacheCreator"] @@ -43,24 +42,18 @@ class KVCacheCreator(Operator): output_schema = KVCacheCreatorOutput def __init__( - self, tokenizer, sequence_length, prompt_sequence_length, internal_kv_cache + self, + tokenizer, + sequence_length: int, + prompt_sequence_length: int, + internal_kv_cache: bool, ): self.tokenizer = tokenizer self.prompt_sequence_length = prompt_sequence_length self.internal_kv_cache = internal_kv_cache self.sequence_length = sequence_length - def run( - self, - inp: Any, - context: Optional[Context], - pipeline_state: PipelineState, - inference_state: InferenceState, - ): - cache_shape = inp.cache_shape - kv_cache_data_type = inp.kv_cache_data_type - output_names = inp.output_names - + def run(self, cache_shape, kv_cache_data_type: str, output_names: list, **kwargs): kv_cache_state = initialize_kv_cache_state( cache_shape=cache_shape, kv_cache_data_type=kv_cache_data_type, @@ -74,4 +67,4 @@ def run( state=kv_cache_state, freeze_first_position=prepends_bos_token(self.tokenizer), ) - return {"kv_cache": kv_cache}, {} + return {"kv_cache": kv_cache} diff --git a/src/deepsparse/v2/text_generation/multi_engine_prefill_operator.py b/src/deepsparse/v2/text_generation/multi_engine_prefill_operator.py index 27752cf04b..41ee830a8a 100644 --- a/src/deepsparse/v2/text_generation/multi_engine_prefill_operator.py +++ b/src/deepsparse/v2/text_generation/multi_engine_prefill_operator.py @@ -12,16 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging from enum import Enum -from typing import Any, Optional +from typing import Any import numpy from deepsparse.transformers.utils.helpers import create_causal_mask from deepsparse.v2.operators import Operator -from deepsparse.v2.utils import Context, InferenceState, PipelineState +from deepsparse.v2.utils import PipelineState +_LOGGER = logging.getLogger(__name__) + __all__ = ["MultiEnginePrefill"] @@ -49,8 +52,12 @@ def __init__(self, prompt_sequence_length, sequence_length): OnnxInputNames.ATTN_MASK.value: self._case_attn_mask, OnnxInputNames.POSITIONS.value: self._case_positions, } + _LOGGER.warn( + "This operator requires the PipelineState to be set-up with the " + "onnx_input_names_no_cache attribute set from the NLEngineOperator." + ) - def can_operate(self, inp: Any, context: Context, inference_state: InferenceState): + def can_operate(self, inp: Any): """ Can only run if the number of prompt tokens left to process is greater than or equal to the self.prompt_sequence_length. @@ -89,15 +96,8 @@ def _case_positions(self, num_total_processed_tokens: int): .astype(numpy.int64) ) - def run( - self, - inp: Any, - context: Optional[Context], - pipeline_state: PipelineState, - inference_state: InferenceState, - ): - tokens = inp.get("tokens") - kv_cache = inp.get("kv_cache") + def run(self, tokens: Any, kv_cache: Any, pipeline_state: PipelineState, **kwargs): + onnx_input_names_no_cache = pipeline_state.current_state.get( "onnx_input_names_no_cache" ) @@ -132,4 +132,4 @@ def run( "engine_inputs": engine_inputs, "kv_cache": kv_cache, "tokens": tokens, - }, {} + } diff --git a/src/deepsparse/v2/text_generation/nl_engine_operator.py b/src/deepsparse/v2/text_generation/nl_engine_operator.py index 1422c50f44..6c1ad1966e 100644 --- a/src/deepsparse/v2/text_generation/nl_engine_operator.py +++ b/src/deepsparse/v2/text_generation/nl_engine_operator.py @@ -14,7 +14,7 @@ import copy import os -from typing import Any, List, Optional, Tuple +from typing import Any, List, Tuple from pydantic import BaseModel, Field @@ -22,12 +22,16 @@ CACHE_INPUT_PREFIX, overwrite_onnx_model_inputs_for_kv_cache_models, ) -from deepsparse.v2.operators.engine_operator import DEEPSPARSE_ENGINE, EngineOperator -from deepsparse.v2.utils import Context, InferenceState, PipelineState +from deepsparse.v2.operators.engine_operator import ( + DEEPSPARSE_ENGINE, + EngineOperator, + EngineOperatorInputs, +) __all__ = ["NLEngineOperator"] + class NlEngineInput(BaseModel): engine_inputs: List = Field(description="engine inputs") kv_cache: Any = Field(description="kv_cache object") @@ -35,6 +39,7 @@ class NlEngineInput(BaseModel): class NLEngineOperator(EngineOperator): + """ Operator for the NL Decoder Engine. This Operator inherits from the EngineOperator. Specific updates to engine attributes are made through this operator, as well @@ -43,13 +48,15 @@ class NLEngineOperator(EngineOperator): """ input_schema = NlEngineInput + output_schema = None - def __init__(self, - sequence_length: int, - input_ids_length: int, - enable_multitoken_prefill: bool, - internal_kv_cache: bool = False, - **kwargs): + def __init__( + self, + sequence_length: int, + input_ids_length: int, + internal_kv_cache: bool = False, + **kwargs, + ): self.kv_cache_data_type = None ( @@ -76,15 +83,14 @@ def __init__(self, ): engine_kwargs["cached_outputs"] = output_indices_to_be_cached - kwargs["engine_kwargs"] = engine_kwargs + kwargs["engine_kwargs"] = engine_kwargs kwargs["model_path"] = onnx_file_path super().__init__(**kwargs) - self.sequence_length = sequence_length self.input_ids_length = input_ids_length - def run(self, inp: NlEngineInput, context: Optional[Context]) -> Any: - engine_input = inp.inputs + def run(self, inp: NlEngineInput, **kwargs) -> Any: + engine_input = inp.engine_inputs kv_cache = inp.kv_cache inputs = self._add_kv_cache_to_input(engine_input, kv_cache) @@ -100,11 +106,10 @@ def run(self, inp: NlEngineInput, context: Optional[Context]) -> Any: ) else: # run the engine without the LIB.kv_cache object - out, _ = super().run( - inputs, - context=context, - pipeline_state=pipeline_state, - inference_state=inference_state, + out = ( + super() + .run(EngineOperatorInputs(engine_inputs=inputs), **kwargs) + .get("engine_outputs") ) logits, *kv_cache_state = out @@ -114,20 +119,18 @@ def run(self, inp: NlEngineInput, context: Optional[Context]) -> Any: kv_cache=kv_cache, ) - output = dict(inp) - output.update({"logits": logits, "kv_cache": kv_cache}) - - return output, {} + output = {"logits": logits, "kv_cache": kv_cache, "tokens": inp.tokens} + return output def _add_kv_cache_to_input(self, engine_input, kv_cache): kv_cache_state = copy.copy(kv_cache.cached_inputs) for idx, input_name in enumerate(self.onnx_input_names_no_cache): - kv_cache_state[input_name] = inp[idx] + kv_cache_state[input_name] = engine_input[idx] new_inp = [kv_cache_state[name] for name in self.engine.input_names] return new_inp - + def _update_kv_cache(self, kv_cache_state, input_ids_len, kv_cache): if bool(kv_cache.engine_internal_cache): kv_cache.total_num_processed_tokens += input_ids_len @@ -186,5 +189,3 @@ def output_names(self) -> List[str]: :return: The output names for the onnx model """ return self.engine.output_names - - \ No newline at end of file diff --git a/src/deepsparse/v2/text_generation/pipeline.py b/src/deepsparse/v2/text_generation/pipeline.py index 05f564353b..9878aa0061 100644 --- a/src/deepsparse/v2/text_generation/pipeline.py +++ b/src/deepsparse/v2/text_generation/pipeline.py @@ -17,7 +17,7 @@ from deepsparse.transformers.utils.helpers import process_generation_config from deepsparse.v2.operators import Operator from deepsparse.v2.pipeline import Pipeline -from deepsparse.v2.routers import TextGenerationRouter +from deepsparse.v2.routers import GraphRouter from deepsparse.v2.schedulers import OperatorScheduler from deepsparse.v2.text_generation import ( AutoRegressiveOperatorPreprocess, @@ -26,7 +26,6 @@ MultiEnginePrefill, NLEngineOperator, PrepareforPrefill, - PrepareforSingleEngine, ProcessInputsTextGeneration, ) from deepsparse.v2.utils import PipelineState @@ -79,12 +78,12 @@ def __init__( # attributes to the specific Operator that neeed them, as class attributes. pipeline_state_vals[ "onnx_input_names_no_cache" - ] = multi_engine_operator.onnx_input_names_no_cache - pipeline_state_vals["cache_shape"] = multi_engine_operator.cache_shape - pipeline_state_vals["output_names"] = multi_engine_operator.output_names + ] = single_engine_operator.onnx_input_names_no_cache + pipeline_state_vals["cache_shape"] = single_engine_operator.cache_shape + pipeline_state_vals["output_names"] = single_engine_operator.output_names pipeline_state_vals[ "kv_cache_data_type" - ] = multi_engine_operator.kv_cache_data_type + ] = single_engine_operator.kv_cache_data_type pipeline_state.create_state(pipeline_state_vals) process_inputs = ProcessInputsTextGeneration( @@ -110,10 +109,12 @@ def __init__( sequence_length=sequence_length, ) compile_prompt_logits = CompilePromptLogits() + """ prep_for_single_engine = PrepareforSingleEngine( prompt_sequence_length=prompt_sequence_length, sequence_length=sequence_length, ) + """ autoregressive_preprocess = AutoRegressiveOperatorPreprocess( sequence_length=sequence_length, prompt_sequence_length=prompt_sequence_length, @@ -128,29 +129,26 @@ def __init__( "prepare_prefill": engine_inputs_for_prefill, "multi_engine_prefill": multi_engine_prefill, "compile_logits": compile_prompt_logits, - "prepare_single_engine": prep_for_single_engine, "autoregressive_preprocess": autoregressive_preprocess, "final_step": final_step, } routes = { "process_input": "prepare_prefill", - "prepare_prefill": ["multi_engine_prefill", "prepare_single_engine"], + "prepare_prefill": ["multi_engine_prefill", "autoregressive_preprocess"], "multi_engine_prefill": "multi_engine", "multi_engine": "compile_logits", "compile_logits": [ "multi_engine_prefill", - "prepare_single_engine", "autoregressive_preprocess", "final_step", ], - "prepare_single_engine": "autoregressive_preprocess", "autoregressive_preprocess": "single_engine", "single_engine": "compile_logits", "final_step": "STOP", } - router = TextGenerationRouter( + router = GraphRouter( end_route="STOP", start_route="process_input", route=routes ) scheduler = [OperatorScheduler()] @@ -208,6 +206,8 @@ def can_operate(self, *args, **kwargs): return True def run(self, *args, **kwargs): + import numpy + inference_state = kwargs.get("inference_state") prompt_logits = inference_state.current_state.get("prompt_logits") - return prompt_logits, {} + return numpy.concatenate(prompt_logits, axis=1) diff --git a/src/deepsparse/v2/text_generation/prefill_preprocess_operator.py b/src/deepsparse/v2/text_generation/prefill_preprocess_operator.py deleted file mode 100644 index 1a6636f581..0000000000 --- a/src/deepsparse/v2/text_generation/prefill_preprocess_operator.py +++ /dev/null @@ -1,21 +0,0 @@ -from pydantic import BaseModel -from deepsparse.v2.operators import operator - -class PrefillPreprocessInput(BaseModel): - engine_inputs: list = Field(description="engine inputs") - -class PrefillPreprocessOutput(BaseModel): - tokens: Any = Field(description="tokens") - kv_cache: DecoderKVCache = Field(description="kv_cache object") - -class PrefillPreprocess(Operator): - input_schema = None - output_schema = None - - def run(self, inp: Any, context: Optional[Context]): - engine_inputs = inp.engine_inputs - - tokens = engine_inputs[0][engine_inputs[1].nonzero()].tolist() - kv_cache = get_kv_cache_decoder(...) # requires engine attributes, engine as input? - - return {"tokens": tokens, "kv_cache": kv_cache} diff --git a/src/deepsparse/v2/text_generation/prep_for_prefill.py b/src/deepsparse/v2/text_generation/prep_for_prefill.py index 264b9859c7..2f9eb15797 100644 --- a/src/deepsparse/v2/text_generation/prep_for_prefill.py +++ b/src/deepsparse/v2/text_generation/prep_for_prefill.py @@ -12,12 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any, Optional +import logging +from typing import Any from deepsparse.v2.operators import Operator -from deepsparse.v2.utils import Context, InferenceState, PipelineState +from deepsparse.v2.utils import PipelineState +_LOGGER = logging.getLogger(__name__) + __all__ = ["PrepareforPrefill"] @@ -26,34 +29,29 @@ def __init__(self, kv_cache_creator: Operator): """ Operator before prefill. Responsible for creating the kv_cache based on engine variables. Currently, this operator expects that the kv_cache_creator is - provided during initization and then uses pipeline state to run the + provided during initization and then uses pipeline_state to run the kv_cache_operator. """ # NOTE: Alternatively, we can initialize the kv_cache_creater operator here, # instead of at the pipeline level. self.kv_cache_creator = kv_cache_creator - def run( - self, - inp: Any, - context: Optional[Context], - pipeline_state: PipelineState, - inference_state: InferenceState, - ): + _LOGGER.warn( + "This operator requires the PipelineState to be set-up with the " + "cache_shape, output_names, kv_cache_data_type attributes to be set " + "from the NLEngineOperator" + ) + + def run(self, tokens: Any, pipeline_state: PipelineState, **kwargs): # NOTE: Can potentially just be class attributes instead of relying on # pipeline state. cache_shape = pipeline_state.current_state.get("cache_shape") data_type = pipeline_state.current_state.get("kv_cache_data_type") output_names = pipeline_state.current_state.get("output_names") - kv_cache, _ = self.kv_cache_creator( - context=context, - pipeline_state=pipeline_state, - inference_state=inference_state, - **{ - "cache_shape": cache_shape, - "kv_cache_data_type": data_type, - "output_names": output_names, - }, - ) - return {"tokens": inp.get("tokens"), "kv_cache": kv_cache.kv_cache}, {} + kv_cache = self.kv_cache_creator.run( + cache_shape=cache_shape, + kv_cache_data_type=data_type, + output_names=output_names, + ).get("kv_cache") + return {"tokens": tokens, "kv_cache": kv_cache} diff --git a/src/deepsparse/v2/text_generation/prep_for_single_engine.py b/src/deepsparse/v2/text_generation/prep_for_single_engine.py deleted file mode 100644 index 6ffd2cc63e..0000000000 --- a/src/deepsparse/v2/text_generation/prep_for_single_engine.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (c) 2021 - present / Neuralmagic, Inc. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from typing import Any, Optional - -from deepsparse.v2.operators import Operator -from deepsparse.v2.utils import Context, InferenceState, PipelineState - - -__all__ = ["PrepareforSingleEngine"] - - -class PrepareforSingleEngine(Operator): - def __init__(self, prompt_sequence_length: int, sequence_length: int): - """ - Prepare to use the single_engine Operator for prompt inference. This requires - updating the kv_cache capacity. - """ - self.prompt_sequence_length = prompt_sequence_length - self.sequence_length = sequence_length - - def can_operate(self, inp: Any, context: Context, inference_state: InferenceState): - # Don't rerun if in autoregessive loop - for c in context.stages_executed: - if c.operator.__class__.__name__ == "AutoRegressiveOperatorPreprocess": - return False - - kv_cache = inp.get("kv_cache") - tokens = inp.get("tokens") - # if 0 prompt tokens remain, can't operate (multi-token engine has already run) - if len(tokens) == kv_cache.total_num_processed_tokens: - return False - - # if number of prompt tokens left to process is >= self.prompt_sequnce_length - # should use the multi_token engine. - if ( - len(tokens) - kv_cache.total_num_processed_tokens - >= self.prompt_sequence_length - ): - return False - return True - - def run( - self, - inp: Any, - context: Optional[Context], - pipeline_state: PipelineState, - inference_state: InferenceState, - ): - kv_cache = inp.get("kv_cache") - kv_cache.set_capacity(self.sequence_length - 1) - - input_values = dict(inp) - input_values.update({"kv_cache": kv_cache}) - return input_values, {} diff --git a/src/deepsparse/v2/text_generation/process_inputs.py b/src/deepsparse/v2/text_generation/process_inputs.py index 588d011254..528dcee0b7 100644 --- a/src/deepsparse/v2/text_generation/process_inputs.py +++ b/src/deepsparse/v2/text_generation/process_inputs.py @@ -13,7 +13,7 @@ # limitations under the License. import pathlib -from typing import Any, Dict, Optional, Union +from typing import Dict, Union import transformers @@ -24,7 +24,6 @@ repeat_inputs, ) from deepsparse.v2.operators import Operator -from deepsparse.v2.utils import Context, InferenceState, PipelineState class GenerationDefaults: @@ -64,13 +63,7 @@ def __init__( self.tokenizer = tokenizer self.sequence_length = sequence_length - def run( - self, - inp: Any, - context: Optional[Context], - pipeline_state: PipelineState, - inference_state: InferenceState, - ): + def run(self, inp: TextGenerationInput, **kwargs): generation_config = check_and_return_generation_config( self.generation_config, inp.generation_config, GenerationDefaults() ) diff --git a/src/deepsparse/v2/utils/__init__.py b/src/deepsparse/v2/utils/__init__.py index a36d8e92ec..358405d7af 100644 --- a/src/deepsparse/v2/utils/__init__.py +++ b/src/deepsparse/v2/utils/__init__.py @@ -13,5 +13,5 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +from .state import * from .types import * diff --git a/tests/deepsparse/v2/test_basic_pipeline.py b/tests/deepsparse/v2/test_basic_pipeline.py index 9f85e4976e..bedddd537a 100644 --- a/tests/deepsparse/v2/test_basic_pipeline.py +++ b/tests/deepsparse/v2/test_basic_pipeline.py @@ -34,7 +34,7 @@ class AddOneOperator(Operator): input_schema = IntSchema output_schema = IntSchema - def run(self, inp: IntSchema) -> Dict: + def run(self, inp: IntSchema, **kwargs) -> Dict: return {"value": inp.value + 1} @@ -42,7 +42,7 @@ class AddTwoOperator(Operator): input_schema = IntSchema output_schema = IntSchema - def run(self, inp: IntSchema) -> Dict: + def run(self, inp: IntSchema, **kwargs) -> Dict: return {"value": inp.value + 2} From 216ceeaf0791b5ae57fa0a7cf2373d919464d66b Mon Sep 17 00:00:00 2001 From: Dipika Sikka Date: Wed, 1 Nov 2023 14:13:02 -0400 Subject: [PATCH 12/12] fix docstrings --- src/deepsparse/v2/operators/operator.py | 6 +++--- src/deepsparse/v2/pipeline.py | 14 +++++++++----- src/deepsparse/v2/routers/router.py | 4 ++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/deepsparse/v2/operators/operator.py b/src/deepsparse/v2/operators/operator.py index c7705743f7..b3963d8223 100644 --- a/src/deepsparse/v2/operators/operator.py +++ b/src/deepsparse/v2/operators/operator.py @@ -65,7 +65,9 @@ def __call__( :param args: an unnamed arg may only be provided if it is of the type of the input_schema - :param context: pipeline context to pass to operator + :param inference_state: inference_state for the pipeline. + :param pipeline_state: pipeline_state for the pipeline. The values in the state + are created during pipeline creation and are read-only during inference. :param kwargs: kwargs when not initializing from an instantiated schema :return: operator output """ @@ -105,8 +107,6 @@ def __call__( @abstractmethod def run(self, *args, **kwargs) -> Any: """ - :param inp: operator input, as the defined input schema if applicable - :param context: pipeline context of already run operators :return: result of this operator as the defined output schema if applicable """ raise NotImplementedError diff --git a/src/deepsparse/v2/pipeline.py b/src/deepsparse/v2/pipeline.py index ee5f796ba7..0a8c8b2f93 100644 --- a/src/deepsparse/v2/pipeline.py +++ b/src/deepsparse/v2/pipeline.py @@ -28,7 +28,7 @@ class Pipeline(Operator): """ Pipeline accepts a series of operators, schedulers, and a router. Calling a pipeline will use the router to run through all the defined operators. The operators should - be implemented using the Operator class and each implemented Operator should be + be implemented using the Operator class and each implemented operator should be responsible for a functional component of the pipelines. The flow of inputs/outputs between the operators and the steps in the pipeline should be defined by the router, (based off of the Router class), which dicates the next operator in the pipeline. @@ -38,6 +38,7 @@ class Pipeline(Operator): or dictionary of operators. :param router: A Router which dictates the next operator to call. :param schedulers: A list of schedulers to run operators. + :param pipeline_state: pipeline_state created during pipeline initialization """ @@ -69,9 +70,9 @@ def run( Run through the operators using the provided router and scheduler. The input to a given operator is the output of the previous operator. - :param inp: input to the operator. expected to be of any type that is - expected by the operator. - + :param inference_state: inference_state for the pipeline. + :param pipeline_state: pipeline_state for the pipeline. The values in the state + are created during pipeline creation and are read-only during inference. """ next_step = self.router.START_ROUTE operator_output = None @@ -115,8 +116,11 @@ def run( def __call__(self, *args, **kwargs): """ + Consolidate any provided inference_state or pipeline_state objects and pass + any other operator inputs to run(). + :return: output of the pipeline operators ran with the router for the given - input + input """ if kwargs.get("inference_state"): inference_state = kwargs.pop("inference_state") diff --git a/src/deepsparse/v2/routers/router.py b/src/deepsparse/v2/routers/router.py index a0a93e6096..d1110d4ca7 100644 --- a/src/deepsparse/v2/routers/router.py +++ b/src/deepsparse/v2/routers/router.py @@ -32,6 +32,7 @@ class Router: :param start_route: the start index or key of the router :param end_route: the end index or key of the router + :param route: the route that the router has to traverse through """ @@ -56,10 +57,9 @@ def next( Determines the index or dictionary key for the next operator which should run. :param past: the previous index or key. This should uniquely determine the next - operator to run + operator to run :param ops: list or dictionary of operators :param inp: operator input - :param inference_state: state variables stores in InferenceState :returns: the next index or dictionary key for the next operator to run """ raise NotImplementedError