Skip to content

Commit e954d21

Browse files
author
DvirDukhan
authored
Merge pull request #792 from RedisAI/fix_script_api_for_1.2
2 parents a472c90 + a3c5ed8 commit e954d21

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1556
-1045
lines changed

docs/commands.md

Lines changed: 83 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,55 @@ redis> > AI._MODELSCAN
408408
2) imagenet:5.0
409409
```
410410

411+
412+
## AI.SCRIPTSTORE
413+
The **`AI.SCRIPTSTORE`** command stores a [TorchScript](https://pytorch.org/docs/stable/jit.html) as the value of a key.
414+
415+
**Redis API**
416+
417+
```
418+
AI.SCRIPTSTORE <key> <device> [TAG tag] ENTRY_POINTS <entry_point_amoint> <entry_point> [<entry_point>...] SOURCE "<script>"
419+
```
420+
421+
_Arguments_
422+
423+
424+
* **key**: the script's key name
425+
* **TAG**: an optional string for tagging the script such as a version number or any arbitrary identifier
426+
* **device**: the device that will execute the model can be of:
427+
* **CPU**: a CPU device
428+
* **GPU**: a GPU device
429+
* **GPU:0**, ..., **GPU:n**: a specific GPU device on a multi-GPU system
430+
* **ENTRY_POINTS** A list of entry points to be used in the script. Each entry point should have the signature of `def entry_point(tensors: List[Tensor], keys: List[str], args: List[str])`. The purpose of each list is as follows:
431+
* `tensors`: A list holding the input tensors to the function.
432+
* `keys`: A list of keys that the torch script is about to preform read/write operations on.
433+
* `args`: A list of additional arguments to the function. If the desired argument is not from type string, it is up to the caller to cast it to the right type, within the script.
434+
* **script**: a string containing [TorchScript](https://pytorch.org/docs/stable/jit.html) source code
435+
436+
_Return_
437+
438+
A simple 'OK' string or an error.
439+
440+
**Examples**
441+
442+
Given the following contents of the file 'addtwo.py':
443+
444+
```python
445+
def addtwo(tensors: List[Tensor], keys: List[str], args: List[str]):
446+
a = tensors[0]
447+
b = tensors[1]
448+
return a + b
449+
```
450+
451+
It can be stored as a RedisAI script using the CPU device with [`redis-cli`](https://redis.io/topics/rediscli) as follows:
452+
453+
```
454+
$ cat addtwo.py | redis-cli -x AI.SCRIPTSET myscript CPU TAG myscript:v0.1 ENTRY_POINTS 1 addtwo SOURCE
455+
OK
456+
```
457+
411458
## AI.SCRIPTSET
459+
_This command is deprecated and will not be available in future versions. consider using AI.SCRIPTSTORE command instead._
412460
The **`AI.SCRIPTSET`** command stores a [TorchScript](https://pytorch.org/docs/stable/jit.html) as the value of a key.
413461

414462
**Redis API**
@@ -471,8 +519,9 @@ An array with alternating entries that represent the following key-value pairs:
471519
!!!!The command returns a list of key-value strings, namely `DEVICE device TAG tag [SOURCE source]`.
472520

473521
1. **DEVICE**: the script's device as a String
474-
1. **TAG**: the scripts's tag as a String
475-
1. **SOURCE**: the script's source code as a String
522+
2. **TAG**: the scripts's tag as a String
523+
3. **SOURCE**: the script's source code as a String
524+
4. **ENTRY_POINTS** will return an array containing the script entry points
476525

477526
**Examples**
478527

@@ -487,6 +536,8 @@ redis> AI.SCRIPTGET myscript
487536
5) "source"
488537
6) def addtwo(a, b):
489538
return a + b
539+
7) "Entry Points"
540+
8) 1) addtwo
490541
```
491542

492543
## AI.SCRIPTDEL
@@ -519,7 +570,7 @@ OK
519570

520571
## AI.SCRIPTEXECUTE
521572

522-
The **`AI.SCRIPTEXECUTE`** command runs a script stored as a key's value on its specified device. It accepts one or more inputs, where the inputs could be tensors stored in RedisAI, int, float, or strings and stores the script outputs as RedisAI tensors if required.
573+
The **`AI.SCRIPTEXECUTE`** command runs a script stored as a key's value on its specified device. It a list of keys, input tensors and addtional script args.
523574

524575
The run request is put in a queue and is executed asynchronously by a worker thread. The client that had issued the run request is blocked until the script run is completed. When needed, tensors data is automatically copied to the device prior to execution.
525576

@@ -532,22 +583,26 @@ A `TIMEOUT t` argument can be specified to cause a request to be removed from th
532583

533584
```
534585
AI.SCRIPTEXECUTE <key> <function>
535-
KEYS n <key> [keys...]
536-
[INPUTS m <input> [input ...] | [LIST_INPUTS l <input> [input ...]]*]
586+
[KEYS n <key> [keys...]]
587+
[INPUTS m <input> [input ...]]
588+
[ARGS k <arg> [arg...]]
537589
[OUTPUTS k <output> [output ...] [TIMEOUT t]]+
538590
```
539591

540592
_Arguments_
541593

542594
* **key**: the script's key name
543595
* **function**: the name of the function to run
544-
* **KEYS**: Either a squence of key names that the script will access before, during and after its execution, or a tag which all those keys share. `KEYS` is a mandatory scope in this command. Redis will verify that all potional key accesses are done to the right shard.
545-
* **INPUTS**: Denotes the beginning of the input parameters list, followed by its length and one or more inputs; The inputs can be tensor key name, `string`, `int` or `float`. The order of the input should be aligned with the order of their respected parameter at the function signature. Note that list inputs are treated in the **LIST_INPUTS** scope.
546-
* **LIST_INPUTS** Denotes the beginning of a list, followed by its length and one or more inputs; The inputs can be tensor key name, `string`, `int` or `float`. The order of the input should be aligned with the order of their respected parameter at the function signature. Note that if more than one list is provided, their order should be aligned with the order of their respected paramter at the function signature.
596+
* **KEYS**: Either a squence of key names that the script will access before, during and after its execution, or a tag which all those keys share.
597+
* **INPUTS**: Denotes the beginning of the input parameters list, followed by its length and one or more input tensors.
598+
* **ARGS**: A list additional arguments that a user can send to the script. All args are sent as strings, but can be casted to other types supported by torch script, such as `int`, or `float`.
547599

548600
* **OUTPUTS**: denotes the beginning of the output tensors keys' list, followed by its length and one or more key names.
549601
* **TIMEOUT**: the time (in ms) after which the client is unblocked and a `TIMEDOUT` string is returned
550602

603+
Note:
604+
Either `KEYS` or `INPUTS` scopes should be provided this command (one or both scopes are acceptable). Those scopes indicate keyspace access and such, the right shard to execute the command at. Redis will verify that all potional key accesses are done to the right shard.
605+
551606
_Return_
552607

553608
A simple 'OK' string, a simple `TIMEDOUT` string, or an error.
@@ -584,10 +639,10 @@ redis> AI.TENSORGET result{tag} VALUES
584639
3) 1) "42"
585640
```
586641

587-
If 'myscript' supports `List[Tensor]` arguments:
642+
An example that supports `List[Tensor]` arguments:
588643
```python
589-
def addn(a, args : List[Tensor]):
590-
return a + torch.stack(args).sum()
644+
def addn(tensors: List[Tensor], keys: List[str], args: List[str]):
645+
return torch.stack(tensors).sum()
591646
```
592647

593648
```
@@ -597,28 +652,32 @@ redis> AI.TENSORSET mytensor2{tag} FLOAT 1 VALUES 1
597652
OK
598653
redis> AI.TENSORSET mytensor3{tag} FLOAT 1 VALUES 1
599654
OK
600-
redis> AI.SCRIPTEXECUTE myscript{tag} addn keys 1 {tag} INPUTS 1 mytensor1{tag} LIST_INPUTS 2 mytensor2{tag} mytensor3{tag} OUTPUTS 1 result{tag}
655+
redis> AI.SCRIPTEXECUTE myscript{tag} addn keys 1 {tag} INPUTS 3 mytensor1{tag} mytensor2{tag} mytensor3{tag} OUTPUTS 1 result{tag}
601656
OK
602657
redis> AI.TENSORGET result{tag} VALUES
603658
1) FLOAT
604659
2) 1) (integer) 1
605660
3) 1) "42"
606661
```
607662

663+
Note: for the time being, as `AI.SCRIPTSET` is still avialable to use, `AI.SCRIPTEXECUTE` still supports running functions that are part of scripts stored with `AI.SCRIPTSET` or imported from old RDB/AOF files. Meaning calling `AI.SCRIPTEXECUTE` over a function without the dedicated signature of `(tensors: List[Tensor], keys: List[str], args: List[str]` will yield a "best effort" execution to match the deprecated API `AI.SCRIPTRUN` function execution. This will map `INPUTS` tensors only, to their counterpart input arguments in the function, according to the order which they apear.
664+
608665
### Redis Commands support.
609-
RedisAI TorchScript now supports simple (non-blocking) Redis commands via the `redis.execute` API. The following (useless) script gets a key name (`x{1}`), and an `int` value (3). First, the script `SET`s the value in the key. Next, the script `GET`s the value back from the key, and sets it in a tensor which is eventually stored under the key 'y{1}'. Note that the inputs are `str` and `int`. The script sets and gets the value and set it into a tensor.
666+
In RedisAI TorchScript now supports simple (non-blocking) Redis commnands via the `redis.execute` API. The following script gets a key name (`x{1}`), and an `int` value (3). First, the script `SET`s the value in the key. Next, the script `GET`s the value back from the key, and sets it in a tensor which is eventually stored under the key 'y{1}'. Note that the inputs are `str` and `int`. The script sets and gets the value and set it into a tensor.
610667

611668
```
612669
def redis_int_to_tensor(redis_value: int):
613670
return torch.tensor(redis_value)
614671
615-
def int_set_get(key:str, value:int):
616-
redis.execute("SET", key, str(value))
672+
def int_set_get(tensors: List[Tensor], keys: List[str], args: List[str]):
673+
key = keys[0]
674+
value = args[0]
675+
redis.execute("SET", key, value)
617676
res = redis.execute("GET", key)
618677
return redis_string_int_to_tensor(res)
619678
```
620679
```
621-
redis> AI.SCRIPTEXECUTE redis_scripts{1} int_set_get KEYS 1 {1} INPUTS 2 x{1} 3 OUTPUTS 1 y{1}
680+
redis> AI.SCRIPTEXECUTE redis_scripts{1} int_set_get KEYS 1 x{1} ARGS 1 3 OUTPUTS 1 y{1}
622681
OK
623682
redis> AI.TENSORGET y{1} VALUES
624683
1) (integer) 3
@@ -764,7 +823,7 @@ It accepts one or more operations, split by the pipe-forward operator (`|>`).
764823

765824
By default, the DAG execution context is local, meaning that tensor keys appearing in the DAG only live in the scope of the command. That is, setting a tensor with `TENSORSET` will store it local memory and not set it to an actual database key. One can refer to that key in subsequent commands within the DAG, but that key won't be visible outside the DAG or to other clients - no keys are open at the database level.
766825

767-
Loading and persisting tensors from/to keyspace should be done explicitly. The user should specify which key tensors to load from keyspace using the `LOAD` keyword, and which command outputs to persist to the keyspace using the `PERSIST` keyspace. The user can also specify keys in Redis that are going to be accessed for read/write operations (for example, from within `AI.SCRIPTEXECUTE` command), by using the keyword `KEYS`.
826+
Loading and persisting tensors from/to keyspace should be done explicitly. The user should specify which key tensors to load from keyspace using the `LOAD` keyword, and which command outputs to persist to the keyspace using the `PERSIST` keyspace. The user can also specify a tag or key which will assist for the routing of the DAG execution on the right shard in Redis that are going to be accessed for read/write operations (for example, from within `AI.SCRIPTEXECUTE` command), by using the keyword `ROUTING`.
768827

769828
As an example, if `command 1` sets a tensor, it can be referenced by any further command on the chaining.
770829

@@ -776,7 +835,7 @@ A `TIMEOUT t` argument can be specified to cause a request to be removed from th
776835
```
777836
AI.DAGEXECUTE [[LOAD <n> <key-1> <key-2> ... <key-n>] |
778837
[PERSIST <n> <key-1> <key-2> ... <key-n>] |
779-
[KEYS <n> <key-1> <key-2> ... <key-n>]]+
838+
[ROUTING <routing_tag>]]
780839
[TIMEOUT t]
781840
|> <command> [|> command ...]
782841
```
@@ -785,9 +844,9 @@ _Arguments_
785844

786845
* **LOAD**: denotes the beginning of the input tensors keys' list, followed by the number of keys, and one or more key names
787846
* **PERSIST**: denotes the beginning of the output tensors keys' list, followed by the number of keys, and one or more key names
788-
* **KEYS**: denotes the beginning of keys' list which are used within this command, followed by the number of keys, and one or more key names. Alternately, the keys names list can be replaced with a tag which all of those keys share. Redis will verify that all potential key accesses are done to the right shard.
847+
* **ROUTING**: denotes the a key name or a tag that will assist in routing the dag execution command to the right shard. Redis will verify that all potential key accesses are done to within the target shard.
789848

790-
_While each of the LOAD, PERSIST and KEYS sections are optional (and may appear at most once in the command), the command must contain **at least one** of these 3 keywords._
849+
_While each of the LOAD, PERSIST and ROUTING sections are optional (and may appear at most once in the command), the command must contain **at least one** of these 3 keywords._
791850
* **TIMEOUT**: an optional argument, denotes the time (in ms) after which the client is unblocked and a `TIMEDOUT` string is returned
792851
* **|> command**: the chaining operator, that denotes the beginning of a RedisAI command, followed by one of RedisAI's commands. Command splitting is done by the presence of another `|>`. The supported commands are:
793852
* `AI.TENSORSET`
@@ -817,17 +876,17 @@ redis> AI.DAGEXECUTE PERSIST 1 predictions{tag} |>
817876
1) OK
818877
2) OK
819878
3) 1) FLOAT
820-
2) 1) (integer) 2
821-
2) (integer) 2
822-
3) "\x00\x00\x80?\x00\x00\x00@\x00\x00@@\x00\x00\x80@"
879+
1) 1) (integer) 2
880+
1) (integer) 2
881+
2) "\x00\x00\x80?\x00\x00\x00@\x00\x00@@\x00\x00\x80@"
823882
```
824883

825884
A common pattern is enqueuing multiple SCRIPTEXECUTE and MODELEXECUTE commands within a DAG. The following example uses ResNet-50,to classify images into 1000 object categories. Given that our input tensor contains each color represented as a 8-bit integer and that neural networks usually work with floating-point tensors as their input we need to cast a tensor to floating-point and normalize the values of the pixels - for that we will use `pre_process_3ch` function.
826885

827886
To optimize the classification process we can use a post process script to return only the category position with the maximum classification - for that we will use `post_process` script. Using the DAG capabilities we've removed the necessity of storing the intermediate tensors in the keyspace. You can even run the entire process without storing the output tensor, as follows:
828887

829888
```
830-
redis> AI.DAGEXECUTE KEYS 1 {tag} |>
889+
redis> AI.DAGEXECUTE ROUTING {tag} |>
831890
AI.TENSORSET image UINT8 224 224 3 BLOB b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00....' |>
832891
AI.SCRIPTEXECUTE imagenet_script{tag} pre_process_3ch INPUTS 1 image OUTPUTS 1 temp_key1 |>
833892
AI.MODELEXECUTE imagenet_model{tag} INPUTS 1 temp_key1 OUTPUTS 1 temp_key2 |>

src/backends/backends.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -316,8 +316,9 @@ int RAI_LoadBackend_Torch(RedisModuleCtx *ctx, const char *path) {
316316
goto error;
317317
}
318318

319-
backend.script_create = (RAI_Script * (*)(const char *, const char *, RAI_Error *))(
320-
unsigned long)dlsym(handle, "RAI_ScriptCreateTorch");
319+
backend.script_create =
320+
(RAI_Script * (*)(const char *, const char *, const char **, size_t, RAI_Error *))(
321+
unsigned long)dlsym(handle, "RAI_ScriptCreateTorch");
321322
if (!_ValidateFuncExists(ctx, backend.script_create, "RAI_ScriptCreateTorch", "TORCH", path)) {
322323
goto error;
323324
}

src/backends/backends.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ typedef struct RAI_LoadedBackend {
6464
int (*model_serialize)(RAI_Model *, char **, size_t *, RAI_Error *);
6565

6666
// ** script_create **: A callback function pointer that creates a script
67-
RAI_Script *(*script_create)(const char *, const char *, RAI_Error *);
67+
RAI_Script *(*script_create)(const char *, const char *, const char **, size_t, RAI_Error *);
6868

6969
// ** script_free **: A callback function pointer that frees a script given
7070
// the RAI_Script pointer

0 commit comments

Comments
 (0)