Skip to content

Commit 8182aa6

Browse files
Merge pull request #68 from RedisAI/scriptexecute_support
SCRIPTEXECUTE and SCRIPTSTORE support
2 parents 2778c16 + c14248b commit 8182aa6

File tree

5 files changed

+288
-18
lines changed

5 files changed

+288
-18
lines changed

redisai/client.py

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,66 @@ def tensorget(
528528
else processor.tensorget(res, as_numpy, as_numpy_mutable, meta_only)
529529
)
530530

531+
def scriptstore(
532+
self, key: AnyStr, device: str, script: str, entry_points: Union[str, Sequence[str]], tag: AnyStr = None
533+
) -> str:
534+
"""
535+
Set the script to RedisAI. The difference from scriptset is that in scriptstore
536+
you must specify entry points within your script. These functions must have specific
537+
signature: 'def entry_point(tensors: List[Tensor], keys: List[str], args: List[str])'.
538+
RedisAI uses the TorchScript engine to execute the script. So the script should
539+
have only TorchScript supported constructs. That being said, it's important to
540+
mention that using redisai script to do post processing or pre processing for a
541+
Tensorflow (or any other backend) is completely valid. For more details about
542+
TorchScript and supported ops, checkout TorchScript documentation.
543+
544+
Parameters
545+
----------
546+
key : AnyStr
547+
Script key at the server
548+
device : str
549+
Device name. Allowed devices are CPU and GPU. If multiple GPUs are available.
550+
it can be specified using the format GPU:<gpu number>. For example: GPU:0
551+
script : str
552+
Script itself, as a Python string
553+
entry_points : Union[str, Sequence[str]]
554+
A list of functions in the script that may serve as entry point for the
555+
execution. Each entry point must have the specify signature:
556+
def entry_point(tensors: List[Tensor], keys: List[str], args: List[str]))
557+
Note that the script may contain additional helper functions that doesn't
558+
have to follow this signature.
559+
tag : AnyStr
560+
Any string that will be saved in RedisAI as tag for the script
561+
562+
Returns
563+
-------
564+
str
565+
'OK' if success, raise an exception otherwise
566+
567+
Note
568+
----
569+
Even though ``script`` is pure Python code, it's a subset of Python language and not
570+
all the Python operations are supported. For more details, checkout TorchScript
571+
documentation. It's also important to note that that the script is executed on a high
572+
performance C++ runtime instead of the Python interpreter. And hence ``script`` should
573+
not have any import statements (A common mistake people make all the time)
574+
575+
Example
576+
-------
577+
>>> script = r'''
578+
>>> def bar(tensors: List[Tensor], keys: List[str], args: List[str]):
579+
>>> a = tensors[0]
580+
>>> b = tensors[1]
581+
>>> return a + b
582+
>>>'''
583+
>>> con.scriptstore('ket', 'cpu', script, 'bar')
584+
'OK'
585+
"""
586+
args = builder.scriptstore(key, device, script, entry_points, tag)
587+
res = self.execute_command(*args)
588+
return res if not self.enable_postprocess else processor.scriptstore(res)
589+
590+
@deprecated(version="1.2.0", reason="Use scriptstore instead")
531591
def scriptset(
532592
self, key: AnyStr, device: str, script: str, tag: AnyStr = None
533593
) -> str:
@@ -622,10 +682,11 @@ def scriptdel(self, key: AnyStr) -> str:
622682
res = self.execute_command(*args)
623683
return res if not self.enable_postprocess else processor.scriptdel(res)
624684

685+
@deprecated(version="1.2.0", reason="Use scriptexecute instead")
625686
def scriptrun(
626687
self,
627688
key: AnyStr,
628-
function: AnyStr,
689+
function: str,
629690
inputs: Union[AnyStr, Sequence[AnyStr]],
630691
outputs: Union[AnyStr, Sequence[AnyStr]],
631692
) -> str:
@@ -636,13 +697,13 @@ def scriptrun(
636697
----------
637698
key : AnyStr
638699
Script key
639-
function : AnyStr
700+
function : str
640701
Name of the function in the ``script``
641702
inputs : Union[AnyStr, List[AnyStr]]
642703
Tensor(s) which is already saved in the RedisAI using a tensorset call. These
643704
tensors will be used as the input for the modelrun
644705
outputs : Union[AnyStr, List[AnyStr]]
645-
keys on which the outputs to be saved. If those keys exist already, modelrun
706+
keys on which the outputs to be saved. If those keys exist already, scriptrun
646707
will overwrite them with new values
647708
648709
Returns
@@ -659,6 +720,62 @@ def scriptrun(
659720
res = self.execute_command(*args)
660721
return res if not self.enable_postprocess else processor.scriptrun(res)
661722

723+
def scriptexecute(
724+
self,
725+
key: AnyStr,
726+
function: str,
727+
keys: Union[AnyStr, Sequence[AnyStr]] = None,
728+
inputs: Union[AnyStr, Sequence[AnyStr]] = None,
729+
args: Union[AnyStr, Sequence[AnyStr]] = None,
730+
outputs: Union[AnyStr, Sequence[AnyStr]] = None,
731+
timeout: int = None,
732+
) -> str:
733+
"""
734+
Run an already set script. Similar to modelexecute.
735+
Must specify keys or inputs.
736+
737+
Parameters
738+
----------
739+
key : AnyStr
740+
Script key
741+
function : str
742+
Name of the function in the ``script``
743+
keys : Union[AnyStr, Sequence[AnyStr]]
744+
Denotes the list of Redis key names that the script will access to
745+
during its execution, for both read and/or write operations.
746+
inputs : Union[AnyStr, Sequence[AnyStr]]
747+
Denotes the input tensors list.
748+
args : Union[AnyStr, Sequence[AnyStr]]
749+
Denotes the list of additional arguments that a user can send to the
750+
script. All args are sent as strings, but can be casted to other types
751+
supported by torch script, such as int, or float.
752+
outputs : Union[AnyStr, List[AnyStr]]
753+
Denotes the output tensors keys' list. If those keys exist already,
754+
scriptexecute will overwrite them with new values.
755+
timeout : int
756+
The max number on milisecinds that may pass before the request is prossced
757+
(meaning that the result will not be computed after that time and TIMEDOUT
758+
is returned in that case).
759+
760+
Returns
761+
-------
762+
str
763+
'OK' if success, raise an exception otherwise
764+
765+
Example
766+
-------
767+
>>> con.scriptexecute('myscript', 'bar', inputs=['a', 'b'], outputs=['c'])
768+
'OK'
769+
>>> con.scriptexecute('myscript{tag}', 'addn',
770+
>>> inputs=['mytensor1{tag}', 'mytensor2{tag}', 'mytensor3{tag}'],
771+
>>> args=['5.0'],
772+
>>> outputs=['result{tag}'])
773+
'OK'
774+
"""
775+
args = builder.scriptexecute(key, function, keys, inputs, args, outputs, timeout)
776+
res = self.execute_command(*args)
777+
return res if not self.enable_postprocess else processor.scriptexecute(res)
778+
662779
def scriptscan(self) -> List[List[AnyStr]]:
663780
"""
664781
Returns the list of all the script in the RedisAI server. Scriptscan API is

redisai/command_builder.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,26 @@ def tensorget(key: AnyStr, as_numpy: bool = True, meta_only: bool = False) -> Se
204204
return args
205205

206206

207+
def scriptstore(
208+
name: AnyStr,
209+
device: str,
210+
script: str,
211+
entry_points: Union[str, Sequence[str]],
212+
tag: AnyStr = None
213+
) -> Sequence:
214+
if device.upper() not in utils.allowed_devices:
215+
raise ValueError(f"Device not allowed. Use any from {utils.allowed_devices}")
216+
if name is None or script is None or entry_points is None:
217+
raise ValueError("Missing required arguments for script store command")
218+
args = ["AI.SCRIPTSTORE", name, device]
219+
if tag:
220+
args += ["TAG", tag]
221+
args += ["ENTRY_POINTS", len(utils.listify(entry_points)), *utils.listify(entry_points)]
222+
args.append("SOURCE")
223+
args.append(script)
224+
return args
225+
226+
207227
def scriptset(name: AnyStr, device: str, script: str, tag: AnyStr = None) -> Sequence:
208228
if device.upper() not in utils.allowed_devices:
209229
raise ValueError(f"Device not allowed. Use any from {utils.allowed_devices}")
@@ -228,10 +248,12 @@ def scriptdel(name: AnyStr) -> Sequence:
228248

229249
def scriptrun(
230250
name: AnyStr,
231-
function: AnyStr,
251+
function: str,
232252
inputs: Union[AnyStr, Sequence[AnyStr]],
233253
outputs: Union[AnyStr, Sequence[AnyStr]],
234254
) -> Sequence:
255+
if name is None or function is None:
256+
raise ValueError("Missing required arguments for script run command")
235257
args = (
236258
"AI.SCRIPTRUN",
237259
name,
@@ -244,6 +266,33 @@ def scriptrun(
244266
return args
245267

246268

269+
def scriptexecute(
270+
name: AnyStr,
271+
function: str,
272+
keys: Union[AnyStr, Sequence[AnyStr]],
273+
inputs: Union[AnyStr, Sequence[AnyStr]],
274+
input_args: Union[AnyStr, Sequence[AnyStr]],
275+
outputs: Union[AnyStr, Sequence[AnyStr]],
276+
timeout: int,
277+
) -> Sequence:
278+
if name is None or function is None or (keys is None and inputs is None):
279+
raise ValueError("Missing required arguments for script execute command")
280+
args = ["AI.SCRIPTEXECUTE", name, function]
281+
282+
if keys is not None:
283+
args += ["KEYS", len(utils.listify(keys)), *utils.listify(keys)]
284+
if inputs is not None:
285+
args += ["INPUTS", len(utils.listify(inputs)), *utils.listify(inputs)]
286+
if input_args is not None:
287+
args += ["ARGS", len(utils.listify(input_args)), *utils.listify(input_args)]
288+
if outputs is not None:
289+
args += ["OUTPUTS", len(utils.listify(outputs)), *utils.listify(outputs)]
290+
if timeout is not None:
291+
args += ["TIMEOUT", timeout]
292+
293+
return args
294+
295+
247296
def scriptscan() -> Sequence:
248297
return ("AI._SCRIPTSCAN",)
249298

redisai/postprocessor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,10 @@ def infoget(res):
7070
"modelrun",
7171
"tensorset",
7272
"scriptset",
73+
"scriptstore",
7374
"scriptdel",
7475
"scriptrun",
76+
"scriptexecute",
7577
"inforeset",
7678
)
7779
for fn in decoding_functions:

redisai/utils.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from typing import AnyStr, ByteString, Callable, List, Sequence, Union
2-
32
import numpy as np
43

54
dtype_dict = {

0 commit comments

Comments
 (0)