Skip to content

Commit 11649ea

Browse files
committed
Refactor tests: use the new commands instead of the deprecated ones. Use "tests_commands" to test the new commands syntax (not for a specific backend), and test the deprecated commands only in "test_deprecated".
Update AI.MODELSTORE documentation.
1 parent 7df134a commit 11649ea

19 files changed

+661
-1009
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Note that Redis config is located at `/usr/local/etc/redis/redis.conf` which can
4646

4747
On the client, set the model
4848
```sh
49-
redis-cli -x AI.MODELSET foo TF CPU INPUTS a b OUTPUTS c BLOB < tests/test_data/graph.pb
49+
redis-cli -x AI.MODELSTORE foo TF CPU INPUTS 2 a b OUTPUTS 1 c BLOB < tests/test_data/graph.pb
5050
```
5151

5252
Then create the input tensors, run the computation graph and get the output tensor (see `load_model.sh`). Note the signatures:

docs/commands.md

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -152,15 +152,15 @@ redis> AI.TENSORGET mytensor META BLOB
152152
!!! important "Using `BLOB` is preferable to `VALUES`"
153153
While it is possible to get the tensor as binary data or numerical values, it is recommended that you use the `BLOB` option. It requires fewer resources and performs better compared to returning the values discretely.
154154

155-
## AI.MODELSET
156-
The **`AI.MODELSET`** commands stores a model as the value of a key.
155+
## AI.MODELSTORE
156+
The **`AI.MODELSTORE`** command stores a model as the value of a key.
157157

158158
**Redis API**
159159

160160
```
161-
AI.MODELSET <key> <backend> <device>
162-
[TAG tag] [BATCHSIZE n [MINBATCHSIZE m]]
163-
[INPUTS <name> ...] [OUTPUTS name ...] BLOB <model>
161+
AI.MODELSTORE <key> <backend> <device>
162+
[TAG tag] [BATCHSIZE n [MINBATCHSIZE m [MINBATCHTIMEOUT t]]]
163+
[INPUTS <input_count> <name> ...] [OUTPUTS <output_count> <name> ...] BLOB <model>
164164
```
165165

166166
_Arguments_
@@ -176,11 +176,13 @@ _Arguments_
176176
* **GPU**: a GPU device
177177
* **GPU:0**, ..., **GPU:n**: a specific GPU device on a multi-GPU system
178178
* **TAG**: an optional string for tagging the model such as a version number or any arbitrary identifier
179-
* **BATCHSIZE**: when provided with an `n` that is greater than 0, the engine will batch incoming requests from multiple clients that use the model with input tensors of the same shape. When `AI.MODELRUN` is called the requests queue is visited and input tensors from compatible requests are concatenated along the 0th (batch) dimension until `n` is exceeded. The model is then run for the entire batch and the results are unpacked back to the individual requests unblocking their respective clients. If the batch size of the inputs to of first request in the queue exceeds `BATCHSIZE`, the request is served immediately (default value: 0).
180-
* **MINBATCHSIZE**: when provided with an `m` that is greater than 0, the engine will postpone calls to `AI.MODELRUN` until the batch's size had reached `m`. In this case, note that requests for which `m` is not reached will hang indefinitely (default value: 0), unless `MINBATCHTIMEOUT` is provided.
181-
* **MINBATCHTIMEOUT**: when provided with a `t` (expressed in milliseconds) that is greater than 0, the engine will trigger a run even though `MINBATCHSIZE` has not been reached after `t` milliseconds from the time a `MODELRUN` (or the enclosing `DAGRUN`) is enqueued. This only applies to cases where both `BATCHSIZE` and `MINBATCHSIZE` are greater than 0.
182-
* **INPUTS**: one or more names of the model's input nodes (applicable only for TensorFlow models)
183-
* **OUTPUTS**: one or more names of the model's output nodes (applicable only for TensorFlow models)
179+
* **BATCHSIZE**: when provided with an `n` that is greater than 0, the engine will batch incoming requests from multiple clients that use the model with input tensors of the same shape. When `AI.MODELEXECUTE` (or `AI.MODELRUN`) is called the requests queue is visited and input tensors from compatible requests are concatenated along the 0th (batch) dimension until `n` is exceeded. The model is then run for the entire batch and the results are unpacked back to the individual requests unblocking their respective clients. If the batch size of the inputs to of first request in the queue exceeds `BATCHSIZE`, the request is served immediately (default value: 0).
180+
* **MINBATCHSIZE**: when provided with an `m` that is greater than 0, the engine will postpone calls to `AI.MODELEXECUTE` until the batch's size had reached `m`. In this case, note that requests for which `m` is not reached will hang indefinitely (default value: 0), unless `MINBATCHTIMEOUT` is provided.
181+
* **MINBATCHTIMEOUT**: when provided with a `t` (expressed in milliseconds) that is greater than 0, the engine will trigger a run even though `MINBATCHSIZE` has not been reached after `t` milliseconds from the time a `MODELEXECUTE` (or the enclosing `DAGRUN`) is enqueued. This only applies to cases where both `BATCHSIZE` and `MINBATCHSIZE` are greater than 0.
182+
* **INPUTS**: denotes that one or more names of the model's input nodes are following (applicable only for TensorFlow models)
183+
* **input_count**: a positive number that indicates the number of following input nodes.
184+
* **OUTPUTS**: denotes that one or more names of the model's output nodes are following (applicable only for TensorFlow models)
185+
* **output_count**: a positive number that indicates the number of following input nodes.
184186
* **model**: the Protobuf-serialized model. Since Redis supports strings up to 512MB, blobs for very large models need to be chunked, e.g. `BLOB chunk1 chunk2 ...`.
185187

186188
_Return_
@@ -192,10 +194,22 @@ A simple 'OK' string or an error.
192194
This example shows to set a model 'mymodel' key using the contents of a local file with [`redis-cli`](https://redis.io/topics/cli). Refer to the [Clients Page](clients.md) for additional client choices that are native to your programming language:
193195

194196
```
195-
$ cat resnet50.pb | redis-cli -x AI.MODELSET mymodel TF CPU TAG imagenet:5.0 INPUTS images OUTPUTS output BLOB
197+
$ cat resnet50.pb | redis-cli -x AI.MODELSTORE mymodel TF CPU TAG imagenet:5.0 INPUTS 1 images OUTPUTS 1 output BLOB
196198
OK
197199
```
198200

201+
## AI.MODELSET
202+
_This command is deprecated and will not be available in future versions. consider using AI.MODELSTORE command instead._
203+
The **`AI.MODELSET`** command stores a model as the value of a key. The command's arguments and effect are both exactly the same as `AI.MODELEXECUTE` command, except that <input_count> and <output_count> arguments should not be specified for TF backend.
204+
205+
**Redis API**
206+
207+
```
208+
AI.MODELSET <key> <backend> <device>
209+
[TAG tag] [BATCHSIZE n [MINBATCHSIZE m [MNBATCHTIMEOUT t]]]
210+
[INPUTS <name> ...] [OUTPUTS name ...] BLOB <model>
211+
```
212+
199213
## AI.MODELGET
200214
The **`AI.MODELGET`** command returns a model's metadata and blob stored as a key's value.
201215

@@ -303,9 +317,9 @@ _Arguments_
303317

304318
* **key**: the model's key name
305319
* **INPUTS**: denotes the beginning of the input tensors keys' list, followed by the number of inputs and one or more key names
306-
* **input_count**: A positive number that indicates the number of following input keys.
320+
* **input_count**: a positive number that indicates the number of following input keys.
307321
* **OUTPUTS**: denotes the beginning of the output tensors keys' list, followed by the number of outputs one or more key names
308-
* **output_count**: A positive number that indicates the number of output keys to follow.
322+
* **output_count**: a positive number that indicates the number of output keys to follow.
309323
* **TIMEOUT**: the time (in ms) after which the client is unblocked and a `TIMEDOUT` string is returned
310324

311325
_Return_

docs/intro.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ RedisAI Tensors are used as inputs and outputs in the execution of models and sc
193193
## Loading Models
194194
A **Model** is a Deep Learning or Machine Learning frozen graph that was generated by some framework. The RedisAI Model data structure represents a DL/ML model that is stored in the database and can be run.
195195

196-
Models, like any other Redis and RedisAI data structures, are identified by keys. A Model's key is created using the [`AI.MODELSET` command](commands.md#aimodelset) and requires the graph payload serialized as protobuf for input.
196+
Models, like any other Redis and RedisAI data structures, are identified by keys. A Model's key is created using the [`AI.MODELSTORE` command](commands.md#aimodelstore) and requires the graph payload serialized as protobuf for input.
197197

198198
In our examples, we'll use one of the graphs that RedisAI uses in its tests, namely 'graph.pb', which can be downloaded from [here](https://github.com/RedisAI/RedisAI/raw/master/tests/test_data/graph.pb). This graph was created using TensorFlow with [this script](https://github.com/RedisAI/RedisAI/blob/master/tests/test_data/tf-minimal.py).
199199

@@ -214,13 +214,13 @@ redis-cli doesn't provide a way to read files' contents, so to load the model wi
214214

215215
```
216216
cat graph.pb | docker exec -i redisai redis-cli -x \
217-
AI.MODELSET mymodel TF CPU INPUTS a b OUTPUTS c BLOB
217+
AI.MODELSTORE mymodel TF CPU INPUTS 2 a b OUTPUTS 1 c BLOB
218218
```
219219

220220
??? example "Example: loading a model from command line"
221221
```
222222
$ cat graph.pb | docker exec -i redisai redis-cli -x \
223-
AI.MODELSET mymodel TF CPU INPUTS a b OUTPUTS c BLOB
223+
AI.MODELSTORE mymodel TF CPU INPUTS 2 a b OUTPUTS 1 c BLOB
224224
OK
225225
```
226226

@@ -230,23 +230,23 @@ cat graph.pb | docker exec -i redisai redis-cli -x \
230230
* [Redis clients page](https://redis.io/clients)
231231
* [RedisAI clients page](clients.md)
232232

233-
Like most commands, `AI.MODELSET`'s first argument is a key's name, which is 'mymodel' in the example. The next two arguments are the model's DL/ML backend and the device it will be executed on. 'graph.pb' in the example is a TensorFlow graph and is denoted by `TF` argument. The model will be executed on the CPU as instructed by the `CPU` argument.
233+
Like most commands, `AI.MODELSTORE`'s first argument is a key's name, which is 'mymodel' in the example. The next two arguments are the model's DL/ML backend and the device it will be executed on. 'graph.pb' in the example is a TensorFlow graph and is denoted by `TF` argument. The model will be executed on the CPU as instructed by the `CPU` argument.
234234

235-
TensorFlow models also require declaring the names of their inputs and outputs. The inputs for 'graph.pb' are called 'a' and 'b', whereas its single output is called 'c'. These names are provided as additional arguments after the 'INPUTS' and 'OUTPUTS' arguments, respectively.
235+
TensorFlow models also require declaring the names of their inputs and outputs. The inputs for 'graph.pb' are called 'a' and 'b', whereas its single output is called 'c'. These names are provided as additional arguments after indicating 'INPUTS' along with the number of inputs to follow, and 'OUTPUTS' along with the number of outputs to follow, respectively.
236236

237237
## Running Models
238-
Once a RedisAI Model key has been set with `AI.MODELSET` it can be run with any Tensor keys from the database as its input. The model's output, after it was executed, is stored in RedisAI Tensors as well.
238+
Once a RedisAI Model key has been set with `AI.MODELSTORE` it can be run with any Tensor keys from the database as its input. The model's output, after it was executed, is stored in RedisAI Tensors as well.
239239

240240
The model stored at 'mymodel' expects two input tensors so we'll use the previously-create 'tA' and create another input tensor, $\begin{equation*} tB = \begin{bmatrix} 3 \\ 5 \end{bmatrix} \end{equation*}$, with the following command:
241241

242242
```
243243
AI.TENSORSET tB FLOAT 2 VALUES 3 5
244244
```
245245

246-
The model can now be run with the [`AI.MODELRUN` command](commands.md#aimodelrun) as follows:
246+
The model can now be run with the [`AI.MODELEXECUTE` command](commands.md#aimodelexecute) as follows:
247247

248248
```
249-
AI.MODELRUN mymodel INPUTS tA tB OUTPUTS tResult
249+
AI.MODELEXECUTE mymodel INPUTS tA tB OUTPUTS tResult
250250
```
251251

252252
!!! example "Example: running a model"
@@ -256,11 +256,11 @@ AI.MODELRUN mymodel INPUTS tA tB OUTPUTS tResult
256256
OK
257257
127.0.0.1:6379> AI.TENSORSET tB FLOAT 2 VALUES 3 5
258258
OK
259-
127.0.0.1:6379> AI.MODELRUN mymodel INPUTS tA tB OUTPUTS tModel
259+
127.0.0.1:6379> AI.MODELEXECUTE mymodel INPUTS 2 tA tB OUTPUTS 1 tModel
260260
OK
261261
```
262262

263-
The first argument to `AI.MODELRUN` is the name of the key at which the RedisAI Model is stored. The names of RedisAI Tensor keys that follow the `INPUTS` argument are used as input for the model. Similarly, following the `OUTPUT` argument are the key names of RedisAI Tensors that the model outputs.
263+
The first argument to `AI.MODELEXECUTE` is the name of the key at which the RedisAI Model is stored. The names of RedisAI Tensor keys that follow the `INPUTS` and `input_count>` arguments are used as input for the model. Similarly, following the `OUTPUTS` and `output_count>`arguments are the key names of RedisAI Tensors that the model outputs.
264264

265265
The inputs for the example are the tensors stored under the 'tA' and 'tB' keys. Once the model's run had finished, a new RedisAI Tensor key called 'tResult' is created and stores the model's output.
266266

src/execution/command_parser.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ static int _ModelExecuteCommand_ParseArgs(RedisModuleCtx *ctx, int argc, RedisMo
6868
RAI_SetError(error, RAI_EMODELRUN, "ERR Invalid argument for output_count");
6969
}
7070
if (noutputs <= 0) {
71-
RAI_SetError(error, RAI_EMODELRUN, "ERR Input count must be a positive integer");
71+
RAI_SetError(error, RAI_EMODELRUN, "ERR Output count must be a positive integer");
7272
return REDISMODULE_ERR;
7373
}
7474
if ((*model)->noutputs != noutputs) {

src/execution/deprecated.c

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -208,13 +208,10 @@ int ModelSetCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
208208
const char *matches[] = {"OUTPUTS"};
209209
AC_GetSliceUntilMatches(&optionsac, &inac, 1, matches);
210210

211-
if (!AC_IsAtEnd(&optionsac)) {
212-
if (!AC_AdvanceIfMatch(&optionsac, "OUTPUTS")) {
213-
return RedisModule_ReplyWithError(ctx, "ERR OUTPUTS not specified");
214-
}
215-
216-
AC_GetSliceToEnd(&optionsac, &outac);
211+
if (AC_IsAtEnd(&optionsac) || !AC_AdvanceIfMatch(&optionsac, "OUTPUTS")) {
212+
return RedisModule_ReplyWithError(ctx, "ERR OUTPUTS not specified");
217213
}
214+
AC_GetSliceToEnd(&optionsac, &outac);
218215
}
219216

220217
size_t ninputs = inac.argc;
@@ -241,12 +238,10 @@ int ModelSetCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
241238

242239
AC_AdvanceUntilMatches(&ac, 1, blob_matches);
243240

244-
if (AC_IsAtEnd(&ac)) {
241+
if (AC_Advance(&ac) != AC_OK || AC_IsAtEnd(&ac)) {
245242
return RedisModule_ReplyWithError(ctx, "ERR Insufficient arguments, missing model BLOB");
246243
}
247244

248-
AC_Advance(&ac);
249-
250245
ArgsCursor blobsac;
251246
AC_GetSliceToEnd(&ac, &blobsac);
252247

src/redisai.c

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ int RedisAI_ModelSet_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv,
146146
* [INPUTS input_count name1 name2 ... OUTPUTS output_count name1 name2 ...] BLOB model_blob
147147
*/
148148
int RedisAI_ModelStore_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
149-
if (argc < 4)
149+
if (argc < 6)
150150
return RedisModule_WrongArity(ctx);
151151

152152
ArgsCursor ac;
@@ -170,13 +170,25 @@ int RedisAI_ModelStore_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **arg
170170
return RedisModule_ReplyWithError(ctx, "ERR unsupported backend");
171171
}
172172

173+
// Parse <backend> argument: check that the device string is "CPU", "GPU",
174+
// "CPU:<n>" or "GPU:<n>, where <n> is a number (contains digits only).
173175
const char *devicestr;
174176
AC_GetString(&ac, &devicestr, NULL, 0);
175-
176-
if (strlen(devicestr) > 10 || strcasecmp(devicestr, "INPUTS") == 0 ||
177-
strcasecmp(devicestr, "OUTPUTS") == 0 || strcasecmp(devicestr, "TAG") == 0 ||
178-
strcasecmp(devicestr, "BATCHSIZE") == 0 || strcasecmp(devicestr, "MINBATCHSIZE") == 0 ||
179-
strcasecmp(devicestr, "MINBATCHTIMEOUT") == 0 || strcasecmp(devicestr, "BLOB") == 0) {
177+
bool valid_device = false;
178+
if (strcasecmp(devicestr, "CPU") == 0 || strcasecmp(devicestr, "GPU") == 0) {
179+
valid_device = true;
180+
} else if ((strncasecmp(devicestr, "GPU:", 4) == 0 || strncasecmp(devicestr, "CPU:", 4) == 0) &&
181+
strlen(devicestr) <= 10) {
182+
bool digits_only = true;
183+
for (size_t i = 5; i < strlen(devicestr); i++) {
184+
if (devicestr[i] < '0' || devicestr[i] > '9') {
185+
digits_only = false;
186+
break;
187+
}
188+
}
189+
valid_device = digits_only;
190+
}
191+
if (!valid_device) {
180192
return RedisModule_ReplyWithError(ctx, "ERR Invalid DEVICE");
181193
}
182194

@@ -208,10 +220,6 @@ int RedisAI_ModelStore_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **arg
208220

209221
unsigned long long minbatchtimeout = 0;
210222
if (AC_AdvanceIfMatch(&ac, "MINBATCHTIMEOUT")) {
211-
if (batchsize == 0) {
212-
return RedisModule_ReplyWithError(ctx,
213-
"ERR MINBATCHTIMEOUT specified without BATCHSIZE");
214-
}
215223
if (minbatchsize == 0) {
216224
return RedisModule_ReplyWithError(ctx,
217225
"ERR MINBATCHTIMEOUT specified without MINBATCHSIZE");
@@ -234,7 +242,6 @@ int RedisAI_ModelStore_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **arg
234242
const char *arg_string;
235243
AC_GetString(&ac, &arg_string, NULL, 0);
236244
unsigned long long ninputs = 0, noutputs = 0;
237-
const char *inputs[ninputs], *outputs[noutputs];
238245

239246
if (backend == RAI_BACKEND_TENSORFLOW) {
240247
if (strcasecmp(arg_string, "INPUTS") != 0) {
@@ -248,10 +255,16 @@ int RedisAI_ModelStore_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **arg
248255
ctx, "ERR number of model inputs does not match the number of "
249256
"given arguments");
250257
}
251-
for (size_t i = 0; i < ninputs; i++) {
252-
AC_GetString(&ac, inputs + i, NULL, 0);
253-
}
258+
} else if (strcasecmp(arg_string, "INPUTS") == 0) {
259+
return RedisModule_ReplyWithError(
260+
ctx, "ERR INPUTS argument should not be specified for this backend");
261+
}
262+
const char *inputs[ninputs];
263+
for (size_t i = 0; i < ninputs; i++) {
264+
AC_GetString(&ac, inputs + i, NULL, 0);
265+
}
254266

267+
if (backend == RAI_BACKEND_TENSORFLOW) {
255268
if (AC_GetString(&ac, &arg_string, NULL, 0) != AC_OK ||
256269
strcasecmp(arg_string, "OUTPUTS") != 0) {
257270
return RedisModule_ReplyWithError(ctx, "ERR OUTPUTS not specified for TF");
@@ -264,16 +277,15 @@ int RedisAI_ModelStore_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **arg
264277
ctx, "ERR number of model outputs does not match the number of "
265278
"given arguments");
266279
}
267-
for (size_t i = 0; i < noutputs; i++) {
268-
AC_GetString(&ac, outputs + i, NULL, 0);
269-
}
280+
}
281+
const char *outputs[noutputs];
282+
for (size_t i = 0; i < noutputs; i++) {
283+
AC_GetString(&ac, outputs + i, NULL, 0);
284+
}
285+
if (backend == RAI_BACKEND_TENSORFLOW) {
270286
AC_GetString(&ac, &arg_string, NULL, 0);
271287
}
272288

273-
if (strcasecmp(arg_string, "INPUTS") == 0 && backend != RAI_BACKEND_TENSORFLOW) {
274-
return RedisModule_ReplyWithError(
275-
ctx, "ERR INPUTS argument should not be specified for this backend");
276-
}
277289
if (strcasecmp(arg_string, "BLOB") != 0) {
278290
return RedisModule_ReplyWithError(ctx, "ERR Invalid argument, expected BLOB");
279291
}

0 commit comments

Comments
 (0)