-
Notifications
You must be signed in to change notification settings - Fork 7k
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
[RFC] Improve Transforms testing codebase #2881
Comments
Thanks for the detailed issue! A few thoughts:
For correctness check, I think it would be preferable if possible to avoid having stored files with expected results, as this is very opaque and might make it more difficult to make modifications or improvements.
I think accimage is less of a priority. It's not maintained and we can probably integrate any potential optimization that is present there with ipp either in torchvision or in PyTorch (only exposed ops are resize and flip, and we should benchmark to see how ipp fares compared to the current CPU implementations in PyTorch).
I think I might be leaning towards the first two options, with maybe a preference for option 2. Wrt the limitations of
I think and approach similar to the custom @configurations([1, 2, 3])
def test_1(self, config):
output = fn(**config)
... |
+1 on using parameterized or a similar solution that supports annotations. Right now we often use complex loops/if-statements in unit-testing. This is generally considered a bad practice and it makes it very difficult for us to debug/pinpoint problems. Here is an example of a test that I wrote in the past that has this issue: Lines 531 to 533 in 005355b
It became quite hard to pinpoint where the problem was when in failed in a recent PR. |
I love the direction this RFC is going. One thing to note regarding the split of devices. The reason why I have decided to split GPU/CUDA tests of torchaudio into separate files is fbcode. CUDA tests cannot run in fbcode, and skipping them constantly will trigger alerts. There is no way in fbcode to disable such alarm because from the viewpoint of fbcode, there is no point of having a test that is constantly skipped. PyTorch-core considers it fine to receive alerts and decided to ignore them (or probably because it's too much work to do such migration), but since torchaudio is small and new, I decided to split them into separate files and run only CPU tests in fbcode. I do no know if the same thing can be accomplished with PyTorch's test utilities. I did not play around with it. |
FYI: Using
import torch
from torch.testing._internal.common_device_type import dtypes
class vision_dtypes(dtypes):
def __init__(self, *args, **kwargs):
super().__init__()
if len(args) > 0 and isinstance(args[0], (list, tuple)):
for arg in args:
assert isinstance(arg, (list, tuple)), \
"When one dtype variant is a tuple or list, " \
"all dtype variants must be. " \
"Received non-list non-tuple dtype {0}".format(str(arg))
assert all(self._is_supported_dtype(dtype) for dtype in arg), "Unknown dtype in {0}".format(str(arg))
else:
assert all(self._is_supported_dtype(arg) for arg in args), "Unknown dtype in {0}".format(str(args))
self.args = args
self.device_type = kwargs.get('device_type', 'all')
@staticmethod
def _is_supported_dtype(dtype):
if isinstance(dtype, torch.dtype):
return True
if isinstance(dtype, str):
# https://pillow.readthedocs.io/en/stable/handbook/concepts.html?highlight=modes#modes
modes = ["1", "L", "P", "RGB", "RGBA", "CMYK", "YCbCr", "LAB", "HSV", "I", "F"]
return dtype in ["PIL.{}".format(mode) for mode in modes]
return False and this can be used as class Tester(TestCase):
@vision_dtypes(*(floating_types() + integral_types() + ("PIL.RGB", "PIL.P", "PIL.F")))
@dtypesIfCUDA(*(floating_types_and_half() + integral_types()))
def test_resize(self, device, dtype, config=None):
# img = self.create_image(h=12, w=16, device=device, dtype=dtype)
pass
instantiate_device_type_tests(Tester, globals())
|
After discussing with @fmassa, we can propose a change to |
@vfdev-5 Would you just like an |
@mruberry yes, something like that would be perfect. The usage will be like that class Tester(TestCase):
@variants([
("nearest_single_value1", {"size": 32, "interpolation": 0}),
("nearest_single_value2", {"size": 25, "interpolation": 0}),
("linear_single_value1", {"size": 32, "interpolation": 2}),
("linear_single_value2", {"size": 25, "interpolation": 2}),
])
@vision_dtypes(*(floating_types() + integral_types() + ("PIL.RGB", "PIL.P", "PIL.F")))
@dtypesIfCUDA(*(floating_types_and_half() + integral_types()))
def test_resize(self, device, dtype, config):
# img = self.create_image(h=12, w=16, device=device, dtype=dtype)
# config = {"size": X, "interpolation": Y}
pass
# instantiate_device_type_tests(Tester, globals())
instantiate_tests(Tester, globals())
>
TesterCPU::test_resize_cpu_float32_nearest_single_value1
TesterCPU::test_resize_cpu_float32_nearest_single_value2
TesterCPU::test_resize_cpu_float32_linear_single_value1
TesterCPU::test_resize_cpu_float32_linear_single_value2 |
Instead of being passed in "config" how do you feel about taking kwargs so the variant params can get splatted? |
I think this could also work. Actually, I've been thinking that it would be nice that something generic like class Tester(TestCase):
@variants("interpolation", [0, 2, 3])
@variants("size", [32, 26, (32, 26), [26, 32]])
@variants("dtype", [floating_types() + integral_types() + ...])
@variants("device", ["cpu", "cuda"])
def test_resize(self, interpolation, size, dtype, device):
# img = self.create_image(h=12, w=16, device=device, dtype=dtype)
pass
ParametrizedTester::test_resize_cpu_float32_size_0_interpolation_0
ParametrizedTester::test_resize_cpu_float32_size_0_interpolation_1
ParametrizedTester::test_resize_cpu_float32_size_0_interpolation_2
ParametrizedTester::test_resize_cpu_float32_size_1_interpolation_0
ParametrizedTester::test_resize_cpu_float32_size_1_interpolation_1
... We can still use |
I agree that would be nice. I'm going to propose updates to PyTorch's test tools soon and they'll include a decorator like |
@mruberry sounds great ! Also, it would be nice to have related classes and methods more configurable. For example, if we would like to derive from # Changed dtypes
class dtypes(object):
# Note: *args, **kwargs for Python2 compat.
# Python 3 allows (self, *args, device_type='all').
def __init__(self, *args, **kwargs):
if len(args) > 0 and isinstance(args[0], (list, tuple)):
for arg in args:
assert isinstance(arg, (list, tuple)), \
"When one dtype variant is a tuple or list, " \
"all dtype variants must be. " \
"Received non-list non-tuple dtype {0}".format(str(arg))
# This is the change vs original dtypes implementation
assert all(self._is_supported_dtype(dtype) for dtype in arg), "Unknown dtype in {0}".format(str(arg))
else:
# This is the change vs original dtypes implementation
assert all(self._is_supported_dtype(arg) for arg in args), "Unknown dtype in {0}".format(str(args))
self.args = args
self.device_type = kwargs.get('device_type', 'all')
def __call__(self, fn):
d = getattr(fn, 'dtypes', {})
assert self.device_type not in d, "dtypes redefinition for {0}".format(self.device_type)
d[self.device_type] = self.args
fn.dtypes = d
return fn
# This is the change vs original dtypes implementation
@staticmethod
def _is_supported_dtype(dtype):
if isinstance(dtype, torch.dtype):
return True
return False I was also thinking to implement |
Changes like that don't need to go through a special planning process, although I am curious if |
@NicolasHug @vfdev-5 It seems to me that some of the changes described on this ticket are now on master. To what degree do you think we covered this ticket? |
@datumbox if we can go with pytest what was not the case on the moment of creating this RFC so we can close this issue. |
🚀 Feature
Current way of transformations testing is not satisfactory due random configurations, duplicated code and unclear structure (test_functional_tensor.py, test_transforms.py, test_transforms_tensor.py). This feature request proposes to rewrite transforms tests in order to takle these problems.
Motivation
Structured approach for transforms tests provides :
Pitch
What do we need:
torchvision.transforms.functional
)torchvision.transforms
)How to do that:
Limitations:
pytest.parametrize
can not be used due to certain internal reasons.1) Inspiration from "Simplify and organize test_ops" PR
Common part of testing a transformation we can defined in special class and derived classes could configure input type etc.
Example from the referenced PR:
2) Use
torch.testing._internal
this gives
Problems:
dtypes
is perfect for torch.Tensor and no simple way to add PIL as dtype3) Parametrized
Packaged looks promising and could potentially solve the limitation of
pytest
(to confirm by fb).this gives
Problems:
4) Another approach inspired from torchaudio tests
Split test into 3 files, for example
We can similarly put a file for PIL input, e.g.
torchscript_consistency_pil_test.py
Open questions
Additional context
Recent bugs (e.g #2869) show unsatisfactory code coverage for transforms.
cc @vfdev-5 @fmassa @datumbox @mthrok
The text was updated successfully, but these errors were encountered: