-
Notifications
You must be signed in to change notification settings - Fork 212
test(train): add unit tests, typing, and docstrings for train utilities #1093
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
Open
YuEfSaEDU
wants to merge
1
commit into
vllm-project:main
Choose a base branch
from
YuEfSaEDU:test-train-utils-1092
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+374
−18
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,25 +1,88 @@ | ||
| """Noise transforms for augmenting tensors (e.g. hidden states) during training.""" | ||
|
|
||
| import torch | ||
|
|
||
| __all__ = [ | ||
| "AddGaussianNoise", | ||
| "AddUniformNoise", | ||
| "TransformTensors", | ||
| ] | ||
|
|
||
|
|
||
| class TransformTensors: | ||
| def __init__(self, std=0.05, tensors=("hidden_states",)): | ||
| """Base class for noise transforms applied to named tensors of a batch. | ||
|
|
||
| Subclasses must override :meth:`transform` to define the noise operation | ||
| applied to each of the configured tensors. | ||
| """ | ||
|
|
||
| def __init__( | ||
| self, std: float = 0.05, tensors: tuple[str, ...] = ("hidden_states",) | ||
| ): | ||
| """Initialize the transform. | ||
|
|
||
| Args: | ||
| std: Scale of the noise applied to the configured tensors. | ||
| tensors: Keys of the batch entries to transform. | ||
| """ | ||
| self.tensors = tensors | ||
| self.std = std | ||
|
|
||
| def __call__(self, data): | ||
| def __call__(self, data: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]: | ||
| """Apply the transform to each configured tensor of the batch. | ||
|
|
||
| Args: | ||
| data: Mapping of batch entries to tensors. Entries listed in | ||
| ``self.tensors`` are replaced with their transformed values. | ||
|
|
||
| Returns: | ||
| The batch mapping with the configured tensors transformed. | ||
| """ | ||
| for tensor in self.tensors: | ||
| data[tensor] = self.transform(data[tensor]) | ||
| return data | ||
|
|
||
| def transform(self, tensor: torch.Tensor) -> torch.Tensor: | ||
| """Apply noise to a single tensor. | ||
|
|
||
| Args: | ||
| tensor: The tensor to transform. | ||
|
|
||
| Returns: | ||
| The transformed tensor. | ||
|
|
||
| Raises: | ||
| NotImplementedError: If the subclass does not implement this method. | ||
| """ | ||
| raise NotImplementedError("Subclasses must implement this method") | ||
|
|
||
|
|
||
| class AddGaussianNoise(TransformTensors): | ||
| """Add zero-mean Gaussian noise to the configured tensors.""" | ||
|
|
||
| def transform(self, tensor: torch.Tensor) -> torch.Tensor: | ||
| """Add Gaussian noise with standard deviation ``self.std`` to a tensor. | ||
|
|
||
| Args: | ||
| tensor: The tensor to transform. | ||
|
|
||
| Returns: | ||
| A new tensor with element-wise Gaussian noise added. | ||
| """ | ||
| return tensor + torch.randn_like(tensor) * self.std | ||
|
|
||
|
|
||
| class AddUniformNoise(TransformTensors): | ||
| """Add uniform noise to the configured tensors.""" | ||
|
|
||
| def transform(self, tensor: torch.Tensor) -> torch.Tensor: | ||
| """Add uniform noise bounded by ``self.std`` to a tensor. | ||
|
|
||
| Args: | ||
| tensor: The tensor to transform. | ||
|
|
||
| Returns: | ||
| A new tensor with element-wise uniform noise added, where each | ||
| noise sample lies within ``[-self.std, self.std)``. | ||
| """ | ||
| return tensor + 2 * (torch.rand_like(tensor) - 0.5) * self.std |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| """Unit tests for the tensor noise transforms used during training.""" | ||
|
|
||
| import pytest | ||
| import torch | ||
|
|
||
| from speculators.train.noise_transforms import ( | ||
| AddGaussianNoise, | ||
| AddUniformNoise, | ||
| TransformTensors, | ||
| ) | ||
|
|
||
|
|
||
| def test_base_transform_raises_not_implemented_error(): | ||
| transform = TransformTensors() | ||
|
|
||
| with pytest.raises(NotImplementedError, match="Subclasses must implement"): | ||
| transform.transform(torch.zeros(2, 2)) | ||
|
|
||
|
|
||
| def test_base_call_raises_not_implemented_error(): | ||
| transform = TransformTensors() | ||
|
|
||
| with pytest.raises(NotImplementedError, match="Subclasses must implement"): | ||
| transform({"hidden_states": torch.zeros(2, 2)}) | ||
|
|
||
|
|
||
| def test_add_gaussian_noise_transforms_only_configured_keys(seed): | ||
| data = { | ||
| "hidden_states": torch.zeros(4, 8), | ||
| "labels": torch.arange(4), | ||
| } | ||
| original_hidden = data["hidden_states"].clone() | ||
| original_labels = data["labels"].clone() | ||
|
|
||
| result = AddGaussianNoise(std=0.1)(data) | ||
|
|
||
| assert result is data | ||
| assert not torch.equal(data["hidden_states"], original_hidden) | ||
| assert torch.equal(data["labels"], original_labels) | ||
|
|
||
|
|
||
| def test_add_gaussian_noise_supports_custom_tensor_keys(seed): | ||
| data = { | ||
| "hidden_states": torch.zeros(2, 2), | ||
| "positions": torch.zeros(2, 2), | ||
| } | ||
|
|
||
| AddGaussianNoise(std=0.1, tensors=("positions",))(data) | ||
|
|
||
| assert torch.equal(data["hidden_states"], torch.zeros(2, 2)) | ||
| assert not torch.equal(data["positions"], torch.zeros(2, 2)) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("dtype", [torch.float32, torch.float64]) | ||
| def test_add_gaussian_noise_preserves_shape_dtype_and_device(seed, dtype): | ||
| tensor = torch.randn(3, 5, 7, dtype=dtype) | ||
|
|
||
| noisy = AddGaussianNoise(std=0.05).transform(tensor) | ||
|
|
||
| assert noisy.shape == tensor.shape | ||
| assert noisy.dtype == tensor.dtype | ||
| assert noisy.device == tensor.device | ||
|
|
||
|
|
||
| def test_add_gaussian_noise_with_zero_std_is_identity(seed): | ||
| tensor = torch.randn(6, 6) | ||
|
|
||
| assert torch.equal(AddGaussianNoise(std=0.0).transform(tensor), tensor) | ||
|
|
||
|
|
||
| def test_add_gaussian_noise_matches_requested_std(seed): | ||
| std = 0.25 | ||
|
|
||
| noise = AddGaussianNoise(std=std).transform(torch.zeros(100, 100)) | ||
|
|
||
| assert noise.std().item() == pytest.approx(std, rel=0.05) | ||
|
|
||
|
|
||
| def test_add_uniform_noise_is_strictly_bounded(seed): | ||
| std = 0.3 | ||
|
|
||
| noise = AddUniformNoise(std=std).transform(torch.zeros(10, 10)) | ||
|
|
||
| # torch.rand samples lie in [0, 1), so the noise lies in [-std, std). | ||
| assert (noise >= -std).all() | ||
| assert (noise < std).all() | ||
| # The noise should span most of the bounded interval. | ||
| assert noise.abs().max() > 0.9 * std | ||
|
|
||
|
|
||
| def test_add_uniform_noise_transforms_only_configured_keys(seed): | ||
| data = { | ||
| "hidden_states": torch.zeros(4, 8), | ||
| "labels": torch.arange(4), | ||
| } | ||
| original_hidden = data["hidden_states"].clone() | ||
| original_labels = data["labels"].clone() | ||
|
|
||
| result = AddUniformNoise(std=0.1)(data) | ||
|
|
||
| assert result is data | ||
| assert not torch.equal(data["hidden_states"], original_hidden) | ||
| assert torch.equal(data["labels"], original_labels) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("dtype", [torch.float32, torch.float64]) | ||
| def test_add_uniform_noise_preserves_shape_dtype_and_device(seed, dtype): | ||
| tensor = torch.randn(2, 3, 4, dtype=dtype) | ||
|
|
||
| noisy = AddUniformNoise(std=0.1).transform(tensor) | ||
|
|
||
| assert noisy.shape == tensor.shape | ||
| assert noisy.dtype == tensor.dtype | ||
| assert noisy.device == tensor.device | ||
|
|
||
|
|
||
| def test_add_uniform_noise_with_zero_std_is_identity(seed): | ||
| tensor = torch.randn(6, 6) | ||
|
|
||
| assert torch.equal(AddUniformNoise(std=0.0).transform(tensor), tensor) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Remove the probabilistic interval-coverage assertion.
The assertions at Line 85 and Line 86 already verify the documented bounds. A valid uniform sample can still have every value within
0.9 * std, so this assertion can fail without an implementation defect. Remove it or replace it with a separately controlled statistical test.Suggested fix
📝 Committable suggestion
🤖 Prompt for AI Agents