Skip to content

Commit 2502530

Browse files
authored
Merge pull request #1 from RedisAI/client-api
Client api
2 parents be64f6c + dc09c43 commit 2502530

File tree

3 files changed

+224
-32
lines changed

3 files changed

+224
-32
lines changed

redisai/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from .client import Client, Type
2-
3-
1+
from .client import (
2+
Client, Tensor, ScalarTensor, BlobTensor, DType, Device, Backend
3+
)

redisai/_util.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import six
22

3+
34
def to_string(s):
45
if isinstance(s, six.string_types):
56
return s

redisai/client.py

Lines changed: 220 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,231 @@
1-
from redis import StrictRedis
2-
from redis._compat import (long, nativestr)
31
from enum import Enum
4-
import six
5-
6-
class Type(Enum):
7-
FLOAT=1
8-
DOUBLE=2
9-
INT8=3
10-
INT16=4
11-
INT32=5
12-
INT64=6
13-
UINT8=7
14-
UINT16=8
15-
2+
from redis import StrictRedis
3+
from ._util import to_string
4+
5+
try:
6+
import numpy as np
7+
except ImportError:
8+
np = None
9+
10+
try:
11+
from typing import Union, Any, AnyStr, ByteString, Collection
12+
except ImportError:
13+
pass
14+
15+
16+
class Device(Enum):
17+
cpu = 'cpu'
18+
gpu = 'gpu'
19+
20+
21+
class Backend(Enum):
22+
tf = 'tf'
23+
torch = 'torch'
24+
onnx = 'ort'
25+
26+
27+
class DType(Enum):
28+
float = 'float'
29+
double = 'double'
30+
int8 = 'int8'
31+
int16 = 'int16'
32+
int32 = 'int32'
33+
int64 = 'int64'
34+
uint8 = 'uint8'
35+
uint16 = 'uint16'
36+
uint32 = 'uint32'
37+
uint64 = 'uint64'
38+
39+
# aliases
40+
float32 = 'float'
41+
float64 = 'double'
1642

17-
class Client(StrictRedis):
1843

19-
def __init__(self, *args, **kwargs):
44+
class Tensor(object):
45+
ARGNAME = 'VALUES'
46+
47+
def __init__(self,
48+
dtype, # type: DType
49+
shape, # type: Collection[int]
50+
value):
51+
"""
52+
Declare a tensor suitable for passing to tensorset
53+
:param dtype: The type the values should be stored as.
54+
This can be one of Tensor.FLOAT, tensor.DOUBLE, etc.
55+
:param shape: An array describing the shape of the tensor. For an
56+
image 250x250 with three channels, this would be [250, 250, 3]
57+
:param value: The value for the tensor. Can be an array.
58+
The contents must coordinate with the shape, meaning that the
59+
overall length needs to be the product of all figures in the
60+
shape. There is no verification to ensure that each dimension
61+
is correct. Your application must ensure that the ordering
62+
is always consistent.
2063
"""
21-
Create a new Client optional host and port
64+
self.type = dtype
65+
self.shape = shape
66+
self.value = value
67+
self._size = 1
68+
if not isinstance(value, (list, tuple)):
69+
self.value = [value]
70+
71+
@property
72+
def size(self):
73+
return self._size
74+
75+
def __repr__(self):
76+
return '<{c.__class__.__name__}(shape={s} type={t}) at 0x{id:x}>'.format(
77+
c=self,
78+
s=self.shape,
79+
t=self.type,
80+
id=id(self))
81+
82+
83+
class ScalarTensor(Tensor):
84+
def __init__(self, dtype, *values):
85+
# type: (ScalarTensor, DType, Any) -> None
86+
"""
87+
Declare a tensor with a bunch of scalar values. This can be used
88+
to 'batch-load' several tensors.
89+
90+
:param dtype: The datatype to store the tensor as
91+
:param values: List of values
92+
"""
93+
super(ScalarTensor, self).__init__(dtype, [1], values)
94+
self._size = len(values)
95+
96+
97+
class BlobTensor(Tensor):
98+
ARGNAME = 'BLOB'
2299

23-
If conn is not None, we employ an already existing redis connection
100+
def __init__(self,
101+
dtype,
102+
shape, # type: Collection[int]
103+
*blobs # type: Union[BlobTensor, ByteString]
104+
):
24105
"""
25-
StrictRedis.__init__(self, *args, **kwargs)
26-
27-
# Set the module commands' callbacks
28-
MODULE_CALLBACKS = {
29-
'AI.TENSORSET': lambda r: r and nativestr(r) == 'OK',
106+
Create a tensor from a binary blob
107+
:param dtype: The datatype, one of Tensor.FLOAT, Tensor.DOUBLE, etc.
108+
:param shape: An array
109+
:param blobs: One or more blobs to assign to the tensor.
110+
"""
111+
if len(blobs) > 1:
112+
blobarr = bytearray()
113+
for b in blobs:
114+
if isinstance(b, BlobTensor):
115+
b = b.value[0]
116+
blobarr += b
117+
size = len(blobs)
118+
blobs = bytes(blobarr)
119+
else:
120+
blobs = bytes(blobs[0])
121+
size = 1
122+
123+
super(BlobTensor, self).__init__(dtype, shape, blobs)
124+
self._size = size
125+
126+
@classmethod
127+
def from_numpy(cls, *nparrs):
128+
# type: (type, np.array) -> BlobTensor
129+
blobs = []
130+
for arr in nparrs:
131+
blobs.append(arr.data)
132+
dt = DType.__members__[str(nparrs[0].dtype)]
133+
return cls(dt, nparrs[0].shape, *blobs)
134+
135+
@property
136+
def blob(self):
137+
return self.value[0]
138+
139+
def to_numpy(self):
140+
# type: () -> np.array
141+
a = np.frombuffer(self.value[0], dtype=self._to_numpy_type(self.type))
142+
return a.reshape(self.shape)
143+
144+
@staticmethod
145+
def _to_numpy_type(t):
146+
t = t.lower()
147+
mm = {
148+
'float': 'float32',
149+
'double': 'float64'
150+
}
151+
if t in mm:
152+
return mm[t]
153+
return t
154+
155+
156+
class Client(StrictRedis):
157+
def modelset(self,
158+
name, # type: AnyStr
159+
backend, # type: Backend
160+
device, # type: Device
161+
inputs, # type: Collection[AnyStr]
162+
outputs, # type: Collection[AnyStr]
163+
data # type: ByteString
164+
):
165+
args = ['AI.MODELSET', name, backend.value, device.value, 'INPUTS']
166+
args += inputs
167+
args += ['OUTPUTS'] + outputs
168+
args += [data]
169+
return self.execute_command(*args)
170+
171+
def modelget(self, name):
172+
rv = self.execute_command('AI.MODELGET', name)
173+
return {
174+
'backend': Backend(rv[0]),
175+
'device': Device(rv[1]),
176+
'data': rv[2]
30177
}
31-
for k, v in six.iteritems(MODULE_CALLBACKS):
32-
self.set_response_callback(k, v)
33178

179+
def modelrun(self, name, inputs, outputs):
180+
args = ['AI.MODELRUN', name]
181+
args += ['INPUTS'] + inputs + ['OUTPUTS'] + outputs
182+
return self.execute_command(*args)
34183

35-
def tensorset(self, key, type, dimensions, tensor):
36-
args = ['AI.TENSORSET', key, type.name] + dimensions + ['VALUES'] + tensor
37-
184+
def tensorset(self, key, tensor):
185+
# type: (Client, AnyStr, Union[Tensor, np.ndarray]) -> Any
186+
"""
187+
Set the values of the tensor on the server using the provided Tensor object
188+
:param key: The name of the tensor
189+
:param tensor: a `Tensor` object
190+
"""
191+
if np and isinstance(tensor, np.ndarray):
192+
tensor = BlobTensor.from_numpy(tensor)
193+
args = ['AI.TENSORSET', key, tensor.type.value, tensor.size]
194+
args += tensor.shape
195+
args += [tensor.ARGNAME]
196+
args += tensor.value
38197
return self.execute_command(*args)
39198

40-
199+
def tensorget(self, key, astype=Tensor, meta_only=False):
200+
"""
201+
Retrieve the value of a tensor from the server
202+
:param key: the name of the tensor
203+
:param astype: the resultant tensor type
204+
:param meta_only: if true, then the value is not retrieved,
205+
only the shape and the type
206+
:return: an instance of astype
207+
"""
208+
argname = 'META' if meta_only else astype.ARGNAME
209+
res = self.execute_command('AI.TENSORGET', key, argname)
210+
dtype, shape = to_string(res[0]), res[1]
211+
if meta_only:
212+
return astype(dtype, shape, [])
213+
else:
214+
return astype(dtype, shape, res[2])
215+
216+
def scriptset(self, name, device, script):
217+
return self.execute_command('AI.SCRIPTSET', name, device.value, script)
218+
219+
def scriptget(self, name):
220+
r = self.execute_command('AI.SCRIPTGET', name)
221+
return {
222+
'device': to_string(r[0]),
223+
'script': to_string(r[1])
224+
}
225+
226+
def scriptrun(self, name, function, inputs, outputs):
227+
args = ['AI.SCRIPTRUN', name, function, 'INPUTS']
228+
args += inputs
229+
args += ['OUTPUTS']
230+
args += outputs
231+
return self.execute_command(*args)

0 commit comments

Comments
 (0)