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

[REVIEW] Implement Feature Request from #1077 on Left Padding #1126

Closed
wants to merge 36 commits into from
Closed
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
a52a90c
Update docstrings for issue #1077
Sep 14, 2021
ed61663
Merge branch 'main' into 1077-implement
Sep 16, 2021
ff1e396
Implementation of left padding for issue #1077
Sep 16, 2021
d9e457d
Update #1077 implementation
Sep 16, 2021
e25c6e8
Implement #1077 update with docstring and type hinting.
Sep 16, 2021
295d4e2
Merge branch 'main' into 1077-implement
lesnikow Sep 16, 2021
5166d57
Merge branch 'main' into 1077-implement
lesnikow Sep 17, 2021
e55336c
Merge branch 'main' into 1077-implement
lesnikow Sep 20, 2021
af8aa57
Merge branch 'main' of github.com:NVIDIA/NVTabular into 1077-implement
Sep 23, 2021
299d356
Update tensorflow module docstring for docs syntax
Sep 23, 2021
1285783
Expose pad_left to user
Sep 24, 2021
364bcf1
Skip test_distributed_multigpu()
Sep 24, 2021
071b8bf
Add unit test for torch dataloader and padding argument
Sep 24, 2021
3cce162
Update torch test for padding argument
Sep 24, 2021
cebb715
Update unit test for padding argument
Sep 25, 2021
5acd76a
Update dataloader torch to pass new tests
Sep 25, 2021
1684289
Clean up loader/torch module
Sep 25, 2021
a319501
Clean up test_torch_dataloader module
Sep 25, 2021
0be389e
Update tests
Sep 27, 2021
d93f9c5
Add tests for the TensorFlow runtime dataloader
Sep 28, 2021
0c0ce69
Implement pad_left in _build_sparse_tensor TF
Sep 28, 2021
941d2f3
Update torch loader documentation
Sep 28, 2021
7944b2a
Merge branch 'main' of 1077-implement
Sep 28, 2021
76c0024
Cleanup _build_sparese_tensor for TF dataloader
Sep 28, 2021
46847cb
Add docstring to _build_sparse_tensor() for tf
Sep 28, 2021
c7ae873
Update docstring
Sep 28, 2021
d86cec3
Refactor torch dataloader pad_left and _build_spar
Sep 28, 2021
d90e1df
Update pytest decorator
Sep 28, 2021
b21c57d
Cleanup torch loader
Sep 28, 2021
2150ede
Implement pad_left with TF ops
Sep 29, 2021
a51aa44
Implement pad_left with TF ops cleanup
Sep 29, 2021
01749f9
Merge branch 'main' into 1077-implement
lesnikow Sep 29, 2021
b305afa
Update tensorflow dataloader implementation
Sep 30, 2021
2febf1a
Merge branch '1077-implement' of https://github.com/NVIDIA/NVTabular …
Sep 30, 2021
587ef0c
Update pad_left TF unit tests
Sep 30, 2021
dd9927e
Update pad_left code for TF sparse tensors
Sep 30, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions nvtabular/loader/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ def __init__(
sparse_names=None,
sparse_max=None,
sparse_as_dense=False,
pad_left=False,
):
self.data = dataset
self.indices = cp.arange(dataset.to_ddf().npartitions)
Expand All @@ -196,6 +197,7 @@ def __init__(
self.sparse_names = sparse_names or []
self.sparse_max = sparse_max or {}
self.sparse_as_dense = sparse_as_dense
self.pad_left = pad_left
self.global_size = global_size or 1
self.global_rank = global_rank or 0

Expand Down
46 changes: 37 additions & 9 deletions nvtabular/loader/tensorflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class KerasSequenceLoader(tf.keras.utils.Sequence, DataLoader):

Iterator output is of the form `(dict(features), list(labels))`,
where each element of the features dict is a
`feature_name: feature_tensor` and each elemtn of the labels
`feature_name: feature_tensor` and each element of the labels
list is a tensor, and all tensors are of shape `(batch_size, 1)`.
Note that this means vectorized continuous and multi-hot categorical
features are not currently supported.
Expand All @@ -153,7 +153,7 @@ class KerasSequenceLoader(tf.keras.utils.Sequence, DataLoader):
workflow.update_stats(dataset.data.to_iter(), record_stats=True)

Parameters
-------------
----------
- paths_or_dataset: str or list(str)
Either a string representing a file pattern (see `tf.glob` for
pattern rules), a list of filenames to be iterated through, or
Expand Down Expand Up @@ -205,6 +205,10 @@ class KerasSequenceLoader(tf.keras.utils.Sequence, DataLoader):
dictionary of key: column_name + value: integer representing max sequence length for column
sparse_dense : bool
bool value to activate transforming sparse tensors to dense
pad_left : bool
Boolean value to indicate whether to pad on the left. Use True to pad on the left,
False to pad on the right. Default: False

"""

_use_nnz = True
Expand All @@ -230,6 +234,7 @@ def __init__(
sparse_names=None,
sparse_max=None,
sparse_as_dense=False,
pad_left=False,
):
dataset = _validate_dataset(
paths_or_dataset, batch_size, buffer_size, engine, reader_kwargs
Expand All @@ -238,7 +243,7 @@ def __init__(
feature_columns, cat_names, cont_names, schema=dataset.schema
)

# sort the ccolumns to avoid getting incorrect output
# Sort the columns to avoid getting incorrect output.
# (https://github.com/NVIDIA/NVTabular/issues/412)
cat_names = _get_embedding_order(cat_names)
cont_names = _get_embedding_order(cont_names)
Expand All @@ -261,23 +266,23 @@ def __init__(
sparse_names=sparse_names,
sparse_max=sparse_max,
sparse_as_dense=sparse_as_dense,
pad_left=pad_left,
)
self._map_fns = []

def __len__(self):
"""
recreating since otherwise Keras yells at you
"""
"""Recreating since otherwise Keras yells at you."""
# TODO: what's a better way to do this inheritance
# of the appropriate methods? A Metaclass?
DataLoader.stop(self)
return DataLoader.__len__(self)

def __getitem__(self, idx):
"""
implemented exclusively for consistency
Implemented exclusively for consistency
with Keras model.fit. Does not leverage
passed idx in any way
passed idx in any way.

"""
return DataLoader.__next__(self)

Expand All @@ -286,6 +291,7 @@ def map(self, fn):
Applying a function to each batch.

This can for instance be used to add `sample_weight` to the model.

"""
self._map_fns.append(fn)

Expand Down Expand Up @@ -416,8 +422,30 @@ def _get_sparse_tensor(self, values, indices, num_rows, seq_limit):
return sparse_tensor

def _build_sparse_tensor(self, values, offsets, diff_offsets, num_rows, seq_limit):
"""Builds sparse tensors in the TensorFlow dataloader.

Parameters
----------
values :
offsets :
diff_offsets :
num_rows :
seq_limit :

Returns
-------
tf.sparse
Our built TensorFlow sparse tensor.

"""
ragged = tf.RaggedTensor.from_row_lengths(values=values, row_lengths=diff_offsets)
tensor = tf.RaggedTensor.from_tensor(ragged.to_tensor(shape=[None, seq_limit])).to_sparse()
if self.pad_left:
max_len = max(max(len(row) for row in ragged), seq_limit)
tensor = tf.stack([tf.pad(row, [[max_len - len(row), 0]]) for row in ragged], axis=0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is iterating through each row the only way to do this? You can actually logically figure out all the padding amounts by doing array math and then you can pass that entire list of "pad_length" entries at once that way its not doing each row, one at a time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good to know. Let me see how to implement this approach you outline here.

Copy link
Contributor Author

@lesnikow lesnikow Sep 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am having a hard time seeing currently a vectorized or further optimized approach to this code snippet. Figuring out the padding amounts by row is not that difficult, but what to do with that tensor of row lengths is very much unclear to me. For instance there is tf.pad(), but this only takes padding tensors of shape [n, 2], where n is the rank of the original tensor, so that we may only do a constant amount of padding per dimension. For torch there is torch.nn.functional.pad(), which also only does a constant amount of left or right padding per dimension. There is tf.ragged's to_tensor() method, which implicitly does padding or truncation according to a given shape, but this only does padding on the right. So it is not that clear how to do a further optimized approach using this variable padding tensor. Did you have some other tensorflow or torch methods that you had in mind, or some other vectorized approach using the variable padding lengths that I am missing? I have tried some others, but to no success yet. In particular composing to_tensor() with tf.tensor.pad() with the pad method using left padding will not work. I can modify the run-length-encoding of the ragged tensor here, but I would guess that this would also not be a vectorized operation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jperez999 How does this current updated implementation of this TensorFlow code snippet without iterating through the rows of the ragged tensor address your feedback?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The for loops you are doing are the problem here I think... That methodology is extremely slow. You have to go in to each row read the data... as opposed to doing the entire column at once.

digits = tf.ragged.constant([[3, 1, 4, 1], [], [5, 9, 2], [6], []])
padded = tf.reverse(digits, [-1]).to_tensor(0)
final_tensor = tf.reverse(padded, [-1])

I think this gives you the padding behavior you want and its a tad bit faster I am clocking this logic in at 0.005383729934692383 and I am clocking your logic at 0.026534557342529297.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jperez999 Did you see the updated TensorFlow implementation that I have here that does not use for loops? It is at commit 01749f9.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that commit you linked there references conda environment files... no code changes... As the PR stands that for loop logic still exists for both torch and tensorflow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That commit is a final merge of main into this branch. The changes are in the predecessors of this commit. They should be viewable here in the web UI or by doing a checkout of that commit. The for loop implementation for TensforFlow is an outdated change. I also tagged you with a comment on the code section below. Are you able to see these changes now?

Copy link
Contributor Author

@lesnikow lesnikow Sep 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In particular, in my web UI, the tensorflow code snippet you are referencing has a yellow "Outdated" box next to it.

else:
tensor = ragged.to_tensor(shape=[None, seq_limit])

tensor = tf.RaggedTensor.from_tensor(tensor).to_sparse()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jperez999 Are you able to see this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see it now... Can you time this compared to the example I gave... would like to see the timing on this execution.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good to hear. Yes one moment please. Is there a particular timing framework or method that you use, for consistency of the timings?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this current code block using tf.concat(), pytest --durations=0 -vv tests/unit/loader/test_tf_dataloader.py -k "test_sparse_tensor_" gives:

=============================================================================================== test session starts =============================================================================================== platform linux -- Python 3.8.10, pytest-6.2.5, py-1.10.0, pluggy-1.0.0 -- /usr/bin/python cachedir: .pytest_cache rootdir: /nvtabular, configfile: pyproject.toml collected 77 items / 75 deselected / 2 selected

tests/unit/loader/test_tf_dataloader.py::test_sparse_tensor_left_padding[False] PASSED [ 50%]
tests/unit/loader/test_tf_dataloader.py::test_sparse_tensor_left_padding[True] PASSED [100%]

================================================================================================ slowest durations ================================================================================================
1.38s call tests/unit/loader/test_tf_dataloader.py::test_sparse_tensor_left_padding[False]
0.09s call tests/unit/loader/test_tf_dataloader.py::test_sparse_tensor_left_padding[True]
0.00s setup tests/unit/loader/test_tf_dataloader.py::test_sparse_tensor_left_padding[True]
0.00s setup tests/unit/loader/test_tf_dataloader.py::test_sparse_tensor_left_padding[False]
0.00s teardown tests/unit/loader/test_tf_dataloader.py::test_sparse_tensor_left_padding[False]
0.00s teardown tests/unit/loader/test_tf_dataloader.py::test_sparse_tensor_left_padding[True]
======================================================================================== 2 passed, 75 deselected in 3.18s =========================================================================================

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a commit with the tf.reverse() code that I can checkout and do this same timing on?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I just meant grabbing that same mini tensor I create in my example (called digits) and run it through your scenario the idea is to get a comparison to see which is the fastest method to ensure that is what we select.

        ragged = tf.ragged.constant([[3, 1, 4, 1], [], [5, 9, 2], [6], []])
        ### from here down your code ###
        non_zero_entries_by_row = tf.math.reduce_sum(ragged / ragged, axis=1)
        paddings = seq_limit - non_zero_entries_by_row.numpy()

        # Make zeros ragged tensor to pad our data tensor with.
        total_entries = ragged.shape[0] * seq_limit
        non_zero_entries = tf.reduce_sum(ragged / ragged).numpy()
        zeros_count = total_entries - non_zero_entries
        zeros_values = tf.zeros(shape=(int(zeros_count)), dtype=tf.dtypes.int64)
        zeros = tf.RaggedTensor.from_row_lengths(values=zeros_values, row_lengths=paddings)

        # Concatenate zeros ragged tensor with our data tensor on either the left or the right,
        # depending on either left_pad or not.
        if self.pad_left:
            tensor = tf.concat([zeros, ragged], axis=1).to_tensor()
        else:
            tensor = tf.concat([ragged, zeros], axis=1).to_tensor()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the code I reported the ~.002 seconds execution time

igits = tf.ragged.constant([[3, 1, 4, 1], [], [5, 9, 2], [6], []])
max_len = max(max(len(row) for row in digits), 7)
tensor = tf.stack([tf.pad(row, [[max_len - len(row), 0]]) for row in digits], axis=0);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, here are the timings I got.

import tensorflow as tf

"""
: %timeit foo()
1.24 ms ± 15.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

: %timeit bar()
24.6 ms ± 1.44 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
"""

def foo():
    seq_limit = 5
    pad_left = True

    digits = tf.ragged.constant([[3, 1, 4, 1], [], [5, 9, 2], [6], []])
    padded = tf.reverse(digits, [-1]).to_tensor(0)
    tensor = tf.reverse(padded, [-1])

    paddings = tf.constant([[0, 0], [2, 0]])
    final_tensor = tf.pad(tensor, paddings)

    return final_tensor


def bar():
    seq_limit = 5
    pad_left = True
    ragged = tf.ragged.constant([[3, 1, 4, 1], [], [5, 9, 2], [6], []])
    #ragged = tf.RaggedTensor.from_row_lengths(values=values, row_lengths=diff_offsets)

    # Get vector of padding lengths using tf ops like reduce_sum.
    non_zero_entries_by_row = tf.math.reduce_sum(ragged / ragged, axis=1)
    paddings = seq_limit - non_zero_entries_by_row.numpy()

    # Make zeros ragged tensor to pad our data tensor with.
    total_entries = ragged.shape[0] * seq_limit
    non_zero_entries = tf.reduce_sum(ragged / ragged).numpy()
    zeros_count = total_entries - non_zero_entries
    zeros_values = tf.zeros(shape=(int(zeros_count)), dtype=tf.dtypes.int32)
    zeros = tf.RaggedTensor.from_row_lengths(values=zeros_values, row_lengths=paddings)

    # Concatenate zeros ragged tensor with our data tensor on either the left or the right,
    # depending on either left_pad or not.
    if pad_left:
        tensor = tf.concat([zeros, ragged], axis=1).to_tensor()
    else:
        tensor = tf.concat([ragged, zeros], axis=1).to_tensor()

    return tensor 

foo() is faster, so I implemented this approach and push a commit along the lines of this approach. Are you able to see this implementation using tf.reverse()?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I see the change... Just need to update torch version now...

if self.sparse_as_dense:
tensor = tf.sparse.to_dense(tensor)
return tensor
Expand Down
50 changes: 48 additions & 2 deletions nvtabular/loader/torch.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class TorchAsyncItr(torch.utils.data.IterableDataset, DataLoader):
batches are the specified size until the final batch.

Parameters
-----------
----------
dataset : NVTabular dataset
cats : [str]
the list of categorical columns in the dataset
Expand All @@ -64,6 +64,10 @@ class TorchAsyncItr(torch.utils.data.IterableDataset, DataLoader):
dictionary of key: column_name + value: integer representing max sequence length for column
sparse_dense : bool
bool value to activate transforming sparse tensors to dense
pad_left : bool
Boolean value to indicate whether to pad on the left. Use True to pad on the left,
False to pad on the right. Default: False

"""

def __init__(
Expand All @@ -83,6 +87,7 @@ def __init__(
sparse_names=None,
sparse_max=None,
sparse_as_dense=False,
pad_left=False,
):
DataLoader.__init__(
self,
Expand All @@ -101,6 +106,7 @@ def __init__(
sparse_names=sparse_names,
sparse_max=sparse_max,
sparse_as_dense=sparse_as_dense,
pad_left=pad_left,
)

def __iter__(self):
Expand Down Expand Up @@ -174,8 +180,48 @@ def _get_sparse_tensor(self, values, indices, num_rows, seq_limit):
sparse_tensor = sparse_tensor.to_dense()
return sparse_tensor

def _build_sparse_tensor(self, values, offsets, diff_offsets, num_rows, seq_limit):
def _build_sparse_tensor_helper_process_column(self, col: torch.Tensor) -> torch.Tensor:
"""Process column by increasing blocks for use in left padding."""
col = col.tolist()
prev, curr = 0, 0
while curr < len(col):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the while loop I am talking about @lesnikow

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good to know, thank you. I have not implemented anything optimized over this yet. I would like to hear your feedback first on the tensorflow side. This torch implementation is also operating on torch.sparse tensors, where torch.functional.nn.pad and torch.flip are not implemented on torch.sparse tensors. Would you have any guidance on how to proceed for this torch.sparse case?

if col[curr] >= col[curr - 1]:
col[prev:curr] = col[prev:curr][::-1]
prev = curr
if curr == (len(col) - 1):
col[prev : curr + 1] = col[prev : curr + 1][::-1]
curr += 1
return torch.Tensor(col)

def _build_sparse_tensor(
self,
values,
offsets,
diff_offsets,
num_rows,
seq_limit,
):
"""Builds sparse tensors in our torch dataloader.

Parameters
----------
values :
offsets :
diff_offsets :
num_rows :
seq_limit :

Returns
-------
torch.sparse
Our built torch sparse tensor.

"""
indices = self._get_indices(offsets, diff_offsets)
if self.pad_left:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again this iteration logic is not the best methodology for covering this: https://stackoverflow.com/questions/48686945/reshaping-a-tensor-with-padding-in-pytorch something like this would be more efficient. torch.nn.functional has a padding function you could use to your advantage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise this is good to know. I will look into how to do this outlined approach for the Torch implementation here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have the same questions for your second comment as I wrote in reply to your first comment above. Would you have any guidance on this here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read over this Stack Overflow question along with all the replies. These approaches will not directly work for this torch implementation. The main obstacle in these approaches is that methods like torch.functional.nn.pad are currently not supported on torch.sparse matrices. Are there any further optimization other than the O(n) linear time that I provided in python code that you see available, such as a vectorized approach, given that these are torch.sparse matrices we are dealing with? I looked through the available methods for this torch.sparse class, and nothing seemed immediately relevant.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still see the while loop here for the torch side is that accurate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has not been updated yet, since I would like to hear your feedback first on the tensorflow side, and since this torch implementation is dealing with torch.sparse tensors instead of tf.RaggedTensors(), the latter of which are easier to implement this for.

Copy link
Contributor Author

@lesnikow lesnikow Sep 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean the for loop? I see no while loop in this code block. Edit: I see this loop now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please see line 187

indices[:, 1] = self._build_sparse_tensor_helper_process_column(
(seq_limit - 1) - indices[:, 1]
)
return self._get_sparse_tensor(values, indices, num_rows, seq_limit)


Expand Down
10 changes: 5 additions & 5 deletions nvtabular/ops/list_slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def transform(self, col_selector: ColumnSelector, df: DataFrameType) -> DataFram
on_cpu = _is_cpu_object(df)
ret = type(df)()
for col in col_selector.names:
# handle CPU via normal python slicing (not very efficient)
# Handle CPU via normal python slicing (not very efficient).
if on_cpu:
ret[col] = [row[self.start : self.end] for row in df[col]]
else:
Expand Down Expand Up @@ -99,8 +99,8 @@ def output_tags(self):

@numba.cuda.jit
def _calculate_row_sizes(start, end, offsets, row_sizes):
"""given a slice (start/end) and existing offsets indicating row lengths, this
calculates the size for each new row after slicing"""
"""Given a slice (start/end) and existing offsets indicating row lengths, this
calculates the size for each new row after slicing."""
rowid = numba.cuda.grid(1)
if rowid < offsets.size - 1:
original_row_size = offsets[rowid + 1] - offsets[rowid]
Expand All @@ -120,9 +120,9 @@ def _calculate_row_sizes(start, end, offsets, row_sizes):

@numba.cuda.jit
def _slice_rows(start, offsets, elements, new_offsets, new_elements):
"""slices rows of a list column. requires the 'new_offsets' to
"""Slices rows of a list column. requires the 'new_offsets' to
be previously calculated (meaning that we don't need the 'end' slice index
since thats baked into the new_offsets"""
since thats baked into the new_offsets."""
rowid = numba.cuda.grid(1)
if rowid < (new_offsets.size - 1):
if start >= 0:
Expand Down
62 changes: 62 additions & 0 deletions tests/unit/loader/test_tf_dataloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,68 @@ def test_sparse_tensors(tmpdir, sparse_dense):
assert not isinstance(feature_tensor, tf.sparse.SparseTensor)


@pytest.mark.parametrize("pad_left", [False, True])
def test_sparse_tensor_left_padding(pad_left):
"""Tests the pad_left functionality of our TensorFlow dataloader
to pad data on the left for sparse tensors."""
df = cudf.DataFrame({"A": [[3, 1, 5, 1], [9, 2], [6]], "B": [[3, 1, 5, 1, 9], [2], [6, 5, 3]]})
categorical_columns = ["A", "B"]
sparse_max = {"A": 5, "B": 8}
batch_size = 4

data_itr = tf_dataloader.KerasSequenceLoader(
nvt.Dataset(df),
cat_names=categorical_columns,
cont_names=[],
label_names=[],
batch_size=batch_size,
sparse_max=sparse_max,
sparse_names=categorical_columns,
sparse_as_dense=True,
pad_left=pad_left,
)

for batch in data_itr:
features, labels = batch
for categorical_column in categorical_columns:
feature_tensor = features[categorical_column]
print("feature_tensor is:\n{}".format(feature_tensor))
print("categorical_column is:\n{}".format(categorical_column))
if pad_left:
if categorical_column == "A":
expected_tensor = tf.constant(
[[0, 3, 1, 5, 1], [0, 0, 0, 9, 2], [0, 0, 0, 0, 6]], dtype=tf.int64
)
print("expected_tensor is:\n{}".format(expected_tensor))
if categorical_column == "B":
expected_tensor = tf.constant(
[
[0, 0, 0, 3, 1, 5, 1, 9],
[0, 0, 0, 0, 0, 0, 0, 2],
[0, 0, 0, 0, 0, 6, 5, 3],
],
dtype=tf.int64,
)
print("expected_tensor is:\n{}".format(expected_tensor))
elif not pad_left:
if categorical_column == "A":
expected_tensor = tf.constant(
[[3, 1, 5, 1, 0], [9, 2, 0, 0, 0], [6, 0, 0, 0, 0]], dtype=tf.int64
)
print("expected_tensor is:\n{}".format(expected_tensor))
if categorical_column == "B":
expected_tensor = tf.constant(
[
[3, 1, 5, 1, 9, 0, 0, 0],
[2, 0, 0, 0, 0, 0, 0, 0],
[6, 5, 3, 0, 0, 0, 0, 0],
],
dtype=tf.int64,
)
print("expected_tensor is:\n{}".format(expected_tensor))
assert tf.experimental.numpy.allclose(feature_tensor, expected_tensor)


@pytest.mark.skipif(
os.environ.get("NR_USER") is not None, reason="not working correctly in ci environment"
)
Expand Down
54 changes: 54 additions & 0 deletions tests/unit/loader/test_torch_dataloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,60 @@ def test_sparse_tensors(sparse_dense):
# ensure they are correct structurally


@pytest.mark.parametrize("pad_left", [False, True])
def test_sparse_tensor_left_padding(pad_left):
"""Tests the pad_left functionality of our Torch dataloader
to pad data on the left for sparse tensors."""
df = cudf.DataFrame({"A": [[3, 1, 5, 1], [9, 2], [6]], "B": [[3, 1, 5, 1, 9], [2], [6, 5, 3]]})
categorical_columns = ["A", "B"]
sparse_max = {"A": 5, "B": 8}
batch_size = 4
data_itr = torch_dataloader.TorchAsyncItr(
nvt.Dataset(df),
cats=categorical_columns,
conts=[],
labels=[],
batch_size=batch_size,
sparse_names=categorical_columns,
sparse_max=sparse_max,
sparse_as_dense=True,
pad_left=pad_left,
)
for batch in data_itr:
features, labels = batch
for categorical_column in categorical_columns:
feature_tensor = features[categorical_column]
if pad_left:
if categorical_column == "A":
expected_tensor = torch.tensor(
[[0, 3, 1, 5, 1], [0, 0, 0, 9, 2], [0, 0, 0, 0, 6]], dtype=torch.int64
).cuda()
if categorical_column == "B":
expected_tensor = torch.tensor(
[
[0, 0, 0, 3, 1, 5, 1, 9],
[0, 0, 0, 0, 0, 0, 0, 2],
[0, 0, 0, 0, 0, 6, 5, 3],
],
dtype=torch.int64,
).cuda()
elif not pad_left:
if categorical_column == "A":
expected_tensor = torch.tensor(
[[3, 1, 5, 1, 0], [9, 2, 0, 0, 0], [6, 0, 0, 0, 0]], dtype=torch.int64
).cuda()
if categorical_column == "B":
expected_tensor = torch.tensor(
[
[3, 1, 5, 1, 9, 0, 0, 0],
[2, 0, 0, 0, 0, 0, 0, 0],
[6, 5, 3, 0, 0, 0, 0, 0],
],
dtype=torch.int64,
).cuda()
assert torch.allclose(feature_tensor, expected_tensor)


def test_mh_model_support(tmpdir):
df = cudf.DataFrame(
{
Expand Down