Skip to content

Commit

Permalink
New version (#27)
Browse files Browse the repository at this point in the history
* new tensor APIs

* container namedtuples

* tflite

* simplified APIs stabilization init

* test cases fix

* todo fixes, LGTM.com fixes, type annotations

* more tests

* supporting np dtypes
  • Loading branch information
Sherin Thomas authored and lantiga committed Dec 6, 2019
1 parent 94d0499 commit 18837ea
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 228 deletions.
26 changes: 10 additions & 16 deletions example.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
from redisai import Client, Tensor, \
BlobTensor, DType, Device, Backend
import numpy as np
from redisai import Client, DType, Device, Backend
import ml2rt

client = Client()
client.tensorset('x', Tensor(DType.float, [2], [2, 3]))
client.tensorset('x', [2, 3], dtype=DType.float)
t = client.tensorget('x')
print(t.value)

model = ml2rt.load_model('test/testdata/graph.pb')
client.tensorset('a', Tensor.scalar(DType.float, 2, 3))
client.tensorset('b', Tensor.scalar(DType.float, 12, 10))
tensor1 = np.array([2, 3], dtype=np.float)
client.tensorset('a', tensor1)
client.tensorset('b', (12, 10), dtype=np.float)
client.modelset('m', Backend.tf,
Device.cpu,
input=['a', 'b'],
output='mul',
inputs=['a', 'b'],
outputs='mul',
data=model)
client.modelrun('m', ['a', 'b'], ['mul'])
print(client.tensorget('mul').value)
print(client.tensorget('mul'))

# Try with a script
script = ml2rt.load_script('test/testdata/script.txt')
client.scriptset('ket', Device.cpu, script)
client.scriptrun('ket', 'bar', input=['a', 'b'], output='c')
client.scriptrun('ket', 'bar', inputs=['a', 'b'], outputs='c')

b1 = client.tensorget('c', as_type=BlobTensor)
b2 = client.tensorget('c', as_type=BlobTensor)

client.tensorset('d', BlobTensor(DType.float, b1.shape, b1, b2))

tnp = b1.to_numpy()
client.tensorset('e', tnp)
1 change: 0 additions & 1 deletion redisai/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from .version import __version__
from .client import Client
from .tensor import Tensor, BlobTensor
from .constants import DType, Device, Backend
75 changes: 35 additions & 40 deletions redisai/client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from redis import StrictRedis
from typing import Union, Any, AnyStr, ByteString, Sequence, Type
from typing import Union, Any, AnyStr, ByteString, Sequence
from .containers import Script, Model, Tensor

try:
import numpy as np
Expand All @@ -8,7 +9,7 @@

from .constants import Backend, Device, DType
from .utils import str_or_strsequence, to_string
from .tensor import Tensor, BlobTensor
from . import convert


class Client(StrictRedis):
Expand Down Expand Up @@ -45,13 +46,12 @@ def modelset(self,
args += [data]
return self.execute_command(*args)

def modelget(self, name: AnyStr) -> dict:
def modelget(self, name: AnyStr) -> Model:
rv = self.execute_command('AI.MODELGET', name)
return {
'backend': Backend(to_string(rv[0])),
'device': Device(to_string(rv[1])),
'data': rv[2]
}
return Model(
rv[2],
Device(to_string(rv[1])),
Backend(to_string(rv[0])))

def modeldel(self, name: AnyStr) -> AnyStr:
return self.execute_command('AI.MODELDEL', name)
Expand All @@ -68,71 +68,66 @@ def modelrun(self,

def tensorset(self,
key: AnyStr,
tensor: Union[Tensor, np.ndarray, list, tuple],
tensor: Union[np.ndarray, list, tuple],
shape: Union[Sequence[int], None] = None,
dtype: Union[DType, None] = None) -> Any:
dtype: Union[DType, type, None] = None) -> Any:
"""
Set the values of the tensor on the server using the provided Tensor object
:param key: The name of the tensor
:param tensor: a `Tensor` object
:param shape: Shape of the tensor
:param dtype: data type of the tensor. Required if input is a sequence of ints/floats
:param tensor: a `np.ndarray` object or python list or tuple
:param shape: Shape of the tensor. Required if `tensor` is list or tuple
:param dtype: data type of the tensor. Required if `tensor` is list or tuple
"""
# TODO: tensorset will not accept BlobTensor or Tensor object in the future.
# Keeping it in the current version for compatibility with the example repo
if np and isinstance(tensor, np.ndarray):
tensor = BlobTensor.from_numpy(tensor)
tensor = convert.from_numpy(tensor)
args = ['AI.TENSORSET', key, tensor.dtype.value, *tensor.shape, tensor.argname, tensor.value]
elif isinstance(tensor, (list, tuple)):
if shape is None:
shape = (len(tensor),)
tensor = Tensor(dtype, shape, tensor)
args = ['AI.TENSORSET', key, tensor.type.value]
args += tensor.shape
args += [tensor.ARGNAME]
args += tensor.value
if not isinstance(dtype, DType):
dtype = DType.__members__[np.dtype(dtype).name]
tensor = convert.from_sequence(tensor, shape, dtype)
args = ['AI.TENSORSET', key, tensor.dtype.value, *tensor.shape, tensor.argname, *tensor.value]
return self.execute_command(*args)

def tensorget(self,
key: AnyStr, as_type: Type[Tensor] = None,
meta_only: bool = False) -> Union[Tensor, BlobTensor]:
key: AnyStr, as_numpy: bool = True,
meta_only: bool = False) -> Union[Tensor, np.ndarray]:
"""
Retrieve the value of a tensor from the server. By default it returns the numpy array
but it can be controlled using `as_type` argument and `meta_only` argument.
:param key: the name of the tensor
:param as_type: the resultant tensor type. Returns numpy array if None
:param as_numpy: Should it return data as numpy.ndarray.
Wraps with namedtuple if False. This flag also decides how to fetch the
value from RedisAI server and could have performance implications
:param meta_only: if true, then the value is not retrieved,
only the shape and the type
:return: an instance of as_type
"""
# TODO; We might remove Tensor & BlobTensor in the future and `tensorget` will return
# python list or numpy arrays or a namedtuple
if meta_only:
argname = 'META'
elif as_type is None:
argname = BlobTensor.ARGNAME
elif as_numpy is True:
argname = 'BLOB'
else:
argname = as_type.ARGNAME
argname = 'VALUES'

res = self.execute_command('AI.TENSORGET', key, argname)
dtype, shape = to_string(res[0]), res[1]
dt = DType.__members__[dtype.lower()]
if meta_only:
return Tensor(dt, shape, [])
elif as_type is None:
return BlobTensor.from_resp(dt, shape, res[2]).to_numpy()
return convert.to_sequence([], shape, dtype)
if as_numpy is True:
return convert.to_numpy(res[2], shape, dtype)
else:
return as_type.from_resp(dt, shape, res[2])
return convert.to_sequence(res[2], shape, dtype)

def scriptset(self, name: AnyStr, device: Device, script: AnyStr) -> AnyStr:
return self.execute_command('AI.SCRIPTSET', name, device.value, script)

def scriptget(self, name: AnyStr) -> dict:
def scriptget(self, name: AnyStr) -> Script:
r = self.execute_command('AI.SCRIPTGET', name)
device = Device(to_string(r[0]))
return {
'device': device,
'script': to_string(r[1])
}
return Script(
to_string(r[1]),
Device(to_string(r[0])))

def scriptdel(self, name):
return self.execute_command('AI.SCRIPTDEL', name)
Expand Down
1 change: 1 addition & 0 deletions redisai/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Backend(Enum):
tf = 'TF'
torch = 'TORCH'
onnx = 'ONNX'
tflite = 'TFLITE'


class DType(Enum):
Expand Down
5 changes: 5 additions & 0 deletions redisai/containers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from collections import namedtuple

Tensor = namedtuple('Tensor', field_names=['value', 'shape', 'dtype', 'argname'])
Script = namedtuple('Script', field_names=['script', 'device'])
Model = namedtuple('Model', field_names=['data', 'device', 'backend'])
43 changes: 43 additions & 0 deletions redisai/convert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from typing import Union, ByteString, Sequence
from .utils import convert_to_num
from .constants import DType
from .containers import Tensor
try:
import numpy as np
except (ImportError, ModuleNotFoundError):
np = None


def from_numpy(tensor: np.ndarray) -> Tensor:
""" Convert the numpy input from user to `Tensor` """
dtype = DType.__members__[str(tensor.dtype)]
shape = tensor.shape
blob = bytes(tensor.data)
return Tensor(blob, shape, dtype, 'BLOB')


def from_sequence(tensor: Sequence, shape: Union[list, tuple], dtype: DType) -> Tensor:
""" Convert the `list`/`tuple` input from user to `Tensor` """
return Tensor(tensor, shape, dtype, 'VALUES')


def to_numpy(value: ByteString, shape: Union[list, tuple], dtype: DType) -> np.ndarray:
""" Convert `BLOB` result from RedisAI to `np.ndarray` """
dtype = DType.__members__[dtype.lower()].value
mm = {
'FLOAT': 'float32',
'DOUBLE': 'float64'
}
if dtype in mm:
dtype = mm[dtype]
else:
dtype = dtype.lower()
a = np.frombuffer(value, dtype=dtype)
return a.reshape(shape)


def to_sequence(value: list, shape: list, dtype: DType) -> Tensor:
""" Convert `VALUES` result from RedisAI to `Tensor` """
dtype = DType.__members__[dtype.lower()]
convert_to_num(dtype, value)
return Tensor(value, tuple(shape), dtype, 'VALUES')
120 changes: 0 additions & 120 deletions redisai/tensor.py

This file was deleted.

2 changes: 1 addition & 1 deletion redisai/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
# 1) we don't load dependencies by storing it in __init__.py
# 2) we can import it in setup.py for the same reason
# 3) we can import it into your module module
__version__ = '0.4.1'
__version__ = '0.5.0'
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

#!/usr/bin/env python
from setuptools import setup, find_packages

Expand Down

0 comments on commit 18837ea

Please sign in to comment.