Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow strings in Python interface for Client.run_script, Client.run_script_multiGPU #407

Merged
merged 13 commits into from
Oct 6, 2023
4 changes: 4 additions & 0 deletions doc/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,26 @@ To be released at some future point in time
Description

- Updated RedisAI version used in post-commit check-in testing in Github pipeline
- Allow strings in Python interface for Client.run_script, Client.run_script_multiGPU
- Improved support for model execution batching
- Added support for model chunking
- Updated the third-party RedisAI component
- Updated the third-party lcov component
- Add link to contributing guidelines
- Added link to contributing guidelines

Detailed Notes

- Updated RedisAI version used in post-commit check-in testing in Github pipeline to a version that supports fetch of model chunking size (PR408_)
- Allow users to pass single keys for the inputs and outputs parameters as a string for Python run_script and run_script_multigpu
- Exposed access to the Redis.AI MINBATCHTIMEOUT parameter, which limits the delay in model execution when trying to accumulate multiple executions in a batch (PR406_)
- Models will now be automatically chunked when sent to/received from the backed database. This allows use of models greater than 511MB in size. (PR404_)
- Updated from RedisAI v1.2.3 (test target)/v1.2.4 and v1.2.5 (CI/CD pipeline) to v1.2.7 (PR402_)
- Updated lcov from version 1.15 to 2.0 (PR396_)
- Create CONTRIBUTIONS.md file that points to the contribution guideline for both SmartSim and SmartRedis (PR395_)

.. _PR408: https://github.com/CrayLabs/SmartRedis/pull/408
.. _PR407: https://github.com/CrayLabs/SmartRedis/pull/407
.. _PR406: https://github.com/CrayLabs/SmartRedis/pull/406
.. _PR404: https://github.com/CrayLabs/SmartRedis/pull/404
.. _PR402: https://github.com/CrayLabs/SmartRedis/pull/402
Expand Down
22 changes: 11 additions & 11 deletions src/python/module/smartredis/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,11 @@ def get_script(self, name: str) -> str:

@exception_handler
def run_script(
self, name: str, fn_name: str, inputs: t.List[str], outputs: t.List[str]
self,
name: str,
fn_name: str,
inputs: t.Union[str, t.List[str]],
outputs: t.Union[str, t.List[str]]
) -> None:
"""Execute TorchScript stored inside the database

Expand All @@ -482,15 +486,13 @@ def run_script(
:param fn_name: name of a function within the script to execute
:type fn_name: str
:param inputs: database tensor names to use as script inputs
:type inputs: list[str]
:type inputs: str | list[str]
:param outputs: database tensor names to receive script outputs
:type outputs: list[str]
:type outputs: str | list[str]
:raises RedisReplyError: if script execution fails
"""
typecheck(name, "name", str)
typecheck(fn_name, "fn_name", str)
typecheck(inputs, "inputs", list)
typecheck(outputs, "outputs", list)
inputs, outputs = self.__check_tensor_args(inputs, outputs)
self._client.run_script(name, fn_name, inputs, outputs)

Expand All @@ -499,8 +501,8 @@ def run_script_multigpu(
self,
name: str,
fn_name: str,
inputs: t.List[str],
outputs: t.List[str],
inputs: t.Union[str, t.List[str]],
outputs: t.Union[str, t.List[str]],
offset: int,
first_gpu: int,
num_gpus: int,
Expand All @@ -519,9 +521,9 @@ def run_script_multigpu(
:param fn_name: name of a function within the script to execute
:type fn_name: str
:param inputs: database tensor names to use as script inputs
:type inputs: list[str]
:type inputs: str | list[str]
:param outputs: database tensor names to receive script outputs
:type outputs: list[str]
:type outputs: str | list[str]
:param offset: index of the current image, such as a processor ID
or MPI rank
:type offset: int
Expand All @@ -533,8 +535,6 @@ def run_script_multigpu(
"""
typecheck(name, "name", str)
typecheck(fn_name, "fn_name", str)
typecheck(inputs, "inputs", list)
typecheck(outputs, "outputs", list)
typecheck(offset, "offset", int)
typecheck(first_gpu, "first_gpu", int)
typecheck(num_gpus, "num_gpus", int)
Expand Down
95 changes: 88 additions & 7 deletions tests/python/test_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@

import numpy as np
import pytest
from os import environ
from smartredis import *
from smartredis.error import *


test_gpu = environ.get("SMARTREDIS_TEST_DEVICE","cpu").lower() == "gpu"

@pytest.fixture
def cfg_opts() -> ConfigOptions:
opts = ConfigOptions.create_from_environment("")
Expand Down Expand Up @@ -88,6 +91,8 @@ def test_bad_function_execution(use_cluster, context):
c.put_tensor("bad-func-tensor", data)
with pytest.raises(RedisReplyError):
c.run_script("bad-function", "bad_function", ["bad-func-tensor"], ["output"])
with pytest.raises(RedisReplyError):
c.run_script("bad-function", "bad_function", "bad-func-tensor", "output")
amandarichardsonn marked this conversation as resolved.
Show resolved Hide resolved


def test_missing_script_function(use_cluster, context):
Expand All @@ -96,11 +101,42 @@ def test_missing_script_function(use_cluster, context):
c = Client(None, use_cluster, logger_name=context)
c.set_function("bad-function", bad_function)
with pytest.raises(RedisReplyError):
c.run_script(
"bad-function", "not-a-function-in-script", ["bad-func-tensor"], ["output"]
)
c.run_script("bad-function", "not-a-function-in-script", ["bad-func-tensor"], ["output"])
with pytest.raises(RedisReplyError):
c.run_script("bad-function", "not-a-function-in-script", "bad-func-tensor", "output")

@pytest.mark.skipif(
not test_gpu,
reason="SMARTREDIS_TEST_DEVICE does not specify 'gpu'"
)
def test_bad_function_execution_multigpu(use_cluster, context):
"""Error raised inside function"""

c = Client(None, use_cluster, logger_name=context)
c.set_function_multigpu("bad-function", bad_function, 0, 1)
data = np.array([1, 2, 3, 4])
c.put_tensor("bad-func-tensor", data)
with pytest.raises(RedisReplyError):
c.run_script_multigpu("bad-function", "bad_function", ["bad-func-tensor"], ["output"], 0, 0, 2)
with pytest.raises(RedisReplyError):
c.run_script_multigpu("bad-function", "bad_function", "bad-func-tensor", "output", 0, 0, 2)


@pytest.mark.skipif(
not test_gpu,
reason="SMARTREDIS_TEST_DEVICE does not specify 'gpu'"
)
def test_missing_script_function_multigpu(use_cluster, context):
"""User requests to run a function not in the script"""

c = Client(None, use_cluster, logger_name=context)
c.set_function_multigpu("bad-function", bad_function, 0, 1)
with pytest.raises(RedisReplyError):
c.run_script_multigpu("bad-function", "not-a-function-in-script", ["bad-func-tensor"], ["output"], 0, 0, 2)
with pytest.raises(RedisReplyError):
c.run_script_multigpu("bad-function", "not-a-function-in-script", "bad-func-tensor", "output", 0, 0, 2)


def test_wrong_model_name(mock_data, mock_model, use_cluster, context):
"""User requests to run a model that is not there"""

Expand Down Expand Up @@ -306,12 +342,12 @@ def test_bad_type_get_script(use_cluster, context):
c.get_script(42)


def test_bad_type_run_script(use_cluster, context):
def test_bad_type_run_script_str(use_cluster, context):
c = Client(None, use_cluster, logger_name=context)
key = "my_script"
fn_name = "phred"
inputs = ["list", "of", "strings"]
outputs = ["another", "string", "list"]
inputs = "a string"
outputs = "another string"
with pytest.raises(TypeError):
c.run_script(42, fn_name, inputs, outputs)
with pytest.raises(TypeError):
Expand All @@ -322,12 +358,28 @@ def test_bad_type_run_script(use_cluster, context):
c.run_script(key, fn_name, inputs, 42)


def test_bad_type_run_script_multigpu(use_cluster, context):
def test_bad_type_run_script_list(use_cluster, context):
c = Client(None, use_cluster, logger_name=context)
key = "my_script"
fn_name = "phred"
inputs = ["list", "of", "strings"]
amandarichardsonn marked this conversation as resolved.
Show resolved Hide resolved
outputs = ["another", "string", "list"]
with pytest.raises(TypeError):
c.run_script(42, fn_name, inputs, outputs)
with pytest.raises(TypeError):
c.run_script(key, 42, inputs, outputs)
with pytest.raises(TypeError):
c.run_script(key, fn_name, 42, outputs)
with pytest.raises(TypeError):
c.run_script(key, fn_name, inputs, 42)


def test_bad_type_run_script_multigpu_str(use_cluster, context):
c = Client(None, use_cluster, logger_name=context)
key = "my_script"
fn_name = "phred"
inputs = "a string"
outputs = "another string"
offset = 0
first_gpu = 0
num_gpus = 1
Expand All @@ -351,6 +403,35 @@ def test_bad_type_run_script_multigpu(use_cluster, context):
c.run_script_multigpu(key, fn_name, inputs, outputs, offset, first_gpu, 0)


def test_bad_type_run_script_multigpu_list(use_cluster, context):
c = Client(None, use_cluster, logger_name=context)
key = "my_script"
fn_name = "phred"
inputs = ["list", "of", "strings"]
outputs = ["another", "string", "list"]
offset = 0
first_gpu = 0
num_gpus = 1
with pytest.raises(TypeError):
c.run_script_multigpu(42, fn_name, inputs, outputs, offset, first_gpu, num_gpus)
with pytest.raises(TypeError):
c.run_script_multigpu(key, 42, inputs, outputs, offset, first_gpu, num_gpus)
with pytest.raises(TypeError):
c.run_script_multigpu(key, fn_name, 42, outputs, offset, first_gpu, num_gpus)
with pytest.raises(TypeError):
c.run_script_multigpu(key, fn_name, inputs, 42, offset, first_gpu, num_gpus)
with pytest.raises(TypeError):
c.run_script_multigpu(key, fn_name, inputs, outputs, "not an integer", first_gpu, num_gpus)
with pytest.raises(TypeError):
c.run_script_multigpu(key, fn_name, inputs, outputs, offset, "not an integer", num_gpus)
with pytest.raises(TypeError):
c.run_script_multigpu(key, fn_name, inputs, outputs, offset, first_gpu, "not an integer")
with pytest.raises(ValueError):
c.run_script_multigpu(key, fn_name, inputs, outputs, offset, -1, num_gpus)
with pytest.raises(ValueError):
c.run_script_multigpu(key, fn_name, inputs, outputs, offset, first_gpu, 0)


def test_bad_type_get_model(use_cluster, context):
c = Client(None, use_cluster, logger_name=context)
with pytest.raises(TypeError):
Expand Down
52 changes: 48 additions & 4 deletions tests/python/test_script_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,19 @@
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import pytest
import inspect
import os.path as osp

from os import environ
import numpy as np
import torch
from smartredis import Client

file_path = osp.dirname(osp.abspath(__file__))


test_gpu = environ.get("SMARTREDIS_TEST_DEVICE","cpu").lower() == "gpu"

def test_set_get_function(use_cluster, context):
c = Client(None, use_cluster, logger_name=context)
c.set_function("test-set-function", one_to_one)
Expand Down Expand Up @@ -63,18 +66,18 @@ def test_set_script_from_file(use_cluster, context):
assert not c.model_exists("test-script-file")


def test_run_script(use_cluster, context):
def test_run_script_str(use_cluster, context):
data = np.array([[1, 2, 3, 4, 5]])

c = Client(None, use_cluster, logger_name=context)
c.put_tensor("script-test-data", data)
c.set_function("one-to-one", one_to_one)
c.run_script("one-to-one", "one_to_one", ["script-test-data"], ["script-test-out"])
c.run_script("one-to-one", "one_to_one", "script-test-data", "script-test-out")
amandarichardsonn marked this conversation as resolved.
Show resolved Hide resolved
out = c.get_tensor("script-test-out")
assert out == 5


def test_run_script_multi(use_cluster, context):
def test_run_script_list(use_cluster, context):
data = np.array([[1, 2, 3, 4]])
data_2 = np.array([[5, 6, 7, 8]])

Expand All @@ -94,6 +97,47 @@ def test_run_script_multi(use_cluster, context):
out, expected, "Returned array from script not equal to expected result"
)

@pytest.mark.skipif(
not test_gpu,
reason="SMARTREDIS_TEST_DEVICE does not specify 'gpu'"
)
def test_run_script_multigpu_str(use_cluster, context):
billschereriii marked this conversation as resolved.
Show resolved Hide resolved
data = np.array([[1, 2, 3, 4, 5]])

c = Client(None, use_cluster, logger_name=context)
c.put_tensor("script-test-data", data)
c.set_function_multigpu("one-to-one", one_to_one, 0, 2)
c.run_script_multigpu("one-to-one", "one_to_one", "script-test-data", "script-test-out", 0, 0, 2)
out = c.get_tensor("script-test-out")
assert out == 5

@pytest.mark.skipif(
not test_gpu,
reason="SMARTREDIS_TEST_DEVICE does not specify 'gpu'"
)
def test_run_script_multigpu_list(use_cluster, context):
billschereriii marked this conversation as resolved.
Show resolved Hide resolved
data = np.array([[1, 2, 3, 4]])
data_2 = np.array([[5, 6, 7, 8]])

c = Client(None, use_cluster, logger_name=context)
c.put_tensor("srpt-multi-out-data-1", data)
c.put_tensor("srpt-multi-out-data-2", data_2)
c.set_function_multigpu("two-to-one", two_to_one, 0, 2)
c.run_script_multigpu(
"two-to-one",
"two_to_one",
["srpt-multi-out-data-1", "srpt-multi-out-data-2"],
["srpt-multi-out-output"],
0,
0,
2
billschereriii marked this conversation as resolved.
Show resolved Hide resolved
)
out = c.get_tensor("srpt-multi-out-output")
expected = np.array([4, 8])
np.testing.assert_array_equal(
out, expected, "Returned array from script not equal to expected result"
)


def one_to_one(data):
"""Sample torchscript script that returns the
Expand Down
Loading