From 07f9064c6acc23bb0b63546edd9e35acea95608c Mon Sep 17 00:00:00 2001 From: Yuanyuan Chen Date: Tue, 8 Sep 2026 13:35:55 +0800 Subject: [PATCH 1/9] Check noise shape and contiguity in rrelu_with_noise_kernel _rrelu_with_noise_xpu_train writes self.numel() distinct values into noise directly. Nothing checked that noise's shape matches self's, or that noise is contiguous; a non-contiguous noise (e.g. an expanded view) got a noise_.contiguous() temporary written into instead and discarded, so the caller's noise tensor silently kept its original values. Add the same shape check the CPU kernel has, and require noise be contiguous so the kernel can write into it directly. CUDA checks neither: its train kernel still writes into a discarded noise_.contiguous() temporary, so a non-contiguous noise silently keeps its old values there as well. --- src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp b/src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp index 3d2e2e5368a..9edc2bf395d 100644 --- a/src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp +++ b/src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp @@ -9,6 +9,7 @@ */ #include +#include #include #include #include @@ -82,7 +83,6 @@ inline void _rrelu_with_noise_xpu_train( const Scalar& upper_, const std::optional& generator) { auto input = input_.contiguous(); - auto noise = noise_.contiguous(); Tensor tmp_output = output.contiguous(); int64_t numel = input.numel(); @@ -103,7 +103,7 @@ inline void _rrelu_with_noise_xpu_train( } const scalar_t* input_data = input.const_data_ptr(); - scalar_t* noise_data = noise.mutable_data_ptr(); + scalar_t* noise_data = noise_.mutable_data_ptr(); scalar_t* output_data = tmp_output.mutable_data_ptr(); double lower = lower_.to(); @@ -169,8 +169,18 @@ Tensor& rrelu_with_noise_kernel( output_arg{output, "output", 3}; checkAllSameGPU( "rrelu_with_noise_out_xpu", {self_arg, noise_arg, output_arg}); + TORCH_CHECK( + self.sym_sizes() == noise.sym_sizes(), + "noise tensor shape must match self tensor shape. Got self.shape = ", + self.sym_sizes(), + " noise.shape = ", + noise.sym_sizes()); if (training) { + // The kernel writes noise directly; a non-contiguous noise (e.g. an + // expanded view) would only have room for a fraction of the + // self.numel() distinct values it writes. + checkContiguous("rrelu_with_noise_out_xpu", noise_arg); AT_DISPATCH_FLOATING_TYPES_AND2( at::ScalarType::Half, at::ScalarType::BFloat16, From 5e7b149d78ce0a0c42abd97a87d192c75de3fe0a Mon Sep 17 00:00:00 2001 From: Yuanyuan Chen Date: Tue, 8 Sep 2026 13:38:21 +0800 Subject: [PATCH 2/9] Trim the noise-contiguity comment --- src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp b/src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp index 9edc2bf395d..7af0d3df8af 100644 --- a/src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp +++ b/src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp @@ -177,9 +177,7 @@ Tensor& rrelu_with_noise_kernel( noise.sym_sizes()); if (training) { - // The kernel writes noise directly; a non-contiguous noise (e.g. an - // expanded view) would only have room for a fraction of the - // self.numel() distinct values it writes. + // The kernel writes noise directly, so it must be a real buffer. checkContiguous("rrelu_with_noise_out_xpu", noise_arg); AT_DISPATCH_FLOATING_TYPES_AND2( at::ScalarType::Half, From 7eb52f466f453527586f5096913b3533189bcf93 Mon Sep 17 00:00:00 2001 From: Yuanyuan Chen Date: Sun, 20 Sep 2026 09:08:16 +0800 Subject: [PATCH 3/9] Add regression tests for the rrelu_with_noise noise checks Exercises the checks added in the previous commit: shape-mismatched and non-contiguous noise must raise, and a contiguous noise must be written by the training kernel. No XPU device locally; verified with ruff: ``` ruff check test/regressions/test_rrelu_with_noise.py ruff format --check --line-length 120 test/regressions/test_rrelu_with_noise.py ``` --- test/regressions/test_rrelu_with_noise.py | 62 +++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 test/regressions/test_rrelu_with_noise.py diff --git a/test/regressions/test_rrelu_with_noise.py b/test/regressions/test_rrelu_with_noise.py new file mode 100644 index 00000000000..1affe5013d7 --- /dev/null +++ b/test/regressions/test_rrelu_with_noise.py @@ -0,0 +1,62 @@ +# Copyright 2020-2026 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 + +# Owner(s): ["module: intel"] +""" +Regression tests for the noise shape/contiguity checks in the XPU +rrelu_with_noise kernel. + +The training kernel writes one noise value per element of self directly +into the caller's noise tensor. Nothing used to check that noise had the +same shape as self or was contiguous, so a non-contiguous noise (e.g. an +expanded view) was silently written through a discarded contiguous +temporary: the returned output was correct but the caller's noise kept +its old values. These tests pin the error checks added for both cases. +""" + +import torch +from torch.testing._internal.common_utils import run_tests, TestCase + +xpu_device = torch.device("xpu") + + +class TestRreluWithNoise(TestCase): + def test_noise_shape_mismatch(self): + x = torch.randn(4, 4, device=xpu_device) + for noise in [ + torch.empty(2, 8, device=xpu_device), + torch.empty(4, 1, device=xpu_device), + ]: + with self.assertRaisesRegex(RuntimeError, "noise tensor shape must match self tensor shape"): + torch.rrelu_with_noise(x, noise, training=True) + + def test_noise_non_contiguous(self): + x = torch.randn(4, 8, device=xpu_device) + noise = torch.randn(4, 16, device=xpu_device)[:, ::2] + self.assertFalse(noise.is_contiguous()) + with self.assertRaisesRegex(RuntimeError, "Expected contiguous tensor"): + torch.rrelu_with_noise(x, noise, training=True) + + # An expanded view: same shape as x, but not a real buffer. + noise = torch.randn(1, 8, device=xpu_device).expand(4, 8) + self.assertFalse(noise.is_contiguous()) + with self.assertRaisesRegex(RuntimeError, "Expected contiguous tensor"): + torch.rrelu_with_noise(x, noise, training=True) + + def test_noise_written(self): + # Positive path: the kernel must write into the caller's noise. + x = torch.randn(64, 64, device=xpu_device) + noise = torch.zeros_like(x) + out = torch.rrelu_with_noise(x, noise, 0.1, 0.9, training=True) + self.assertTrue(torch.all(noise[x > 0] == 1)) + self.assertTrue(torch.all((noise >= 0.1) & (noise <= 0.9))) + self.assertTrue(torch.all(out >= 0)) + + +if __name__ == "__main__": + run_tests() From 1f844bafef035ec8eae7848ad02adf0f94c1e7d0 Mon Sep 17 00:00:00 2001 From: Yuanyuan Chen Date: Wed, 23 Sep 2026 09:21:22 +0800 Subject: [PATCH 4/9] Materialize non-contiguous noise instead of rejecting it --- .../native/xpu/sycl/RreluWithNoiseKernels.cpp | 9 ++++---- test/regressions/test_rrelu_with_noise.py | 23 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp b/src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp index 7af0d3df8af..2d79211df15 100644 --- a/src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp +++ b/src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp @@ -9,7 +9,6 @@ */ #include -#include #include #include #include @@ -84,6 +83,7 @@ inline void _rrelu_with_noise_xpu_train( const std::optional& generator) { auto input = input_.contiguous(); Tensor tmp_output = output.contiguous(); + Tensor tmp_noise = noise_.contiguous(); int64_t numel = input.numel(); constexpr int unroll_factor = std::is_same_v ? 2 : 4; @@ -103,7 +103,7 @@ inline void _rrelu_with_noise_xpu_train( } const scalar_t* input_data = input.const_data_ptr(); - scalar_t* noise_data = noise_.mutable_data_ptr(); + scalar_t* noise_data = tmp_noise.mutable_data_ptr(); scalar_t* output_data = tmp_output.mutable_data_ptr(); double lower = lower_.to(); @@ -149,6 +149,9 @@ inline void _rrelu_with_noise_xpu_train( if (!output.is_contiguous()) { output.copy_(tmp_output); } + if (!noise_.is_contiguous()) { + noise_.copy_(tmp_noise); + } } Tensor& rrelu_with_noise_kernel( @@ -177,8 +180,6 @@ Tensor& rrelu_with_noise_kernel( noise.sym_sizes()); if (training) { - // The kernel writes noise directly, so it must be a real buffer. - checkContiguous("rrelu_with_noise_out_xpu", noise_arg); AT_DISPATCH_FLOATING_TYPES_AND2( at::ScalarType::Half, at::ScalarType::BFloat16, diff --git a/test/regressions/test_rrelu_with_noise.py b/test/regressions/test_rrelu_with_noise.py index 1affe5013d7..0ed12b2acfb 100644 --- a/test/regressions/test_rrelu_with_noise.py +++ b/test/regressions/test_rrelu_with_noise.py @@ -8,15 +8,16 @@ # Owner(s): ["module: intel"] """ -Regression tests for the noise shape/contiguity checks in the XPU -rrelu_with_noise kernel. +Regression tests for the noise shape check and non-contiguous noise +handling in the XPU rrelu_with_noise kernel. The training kernel writes one noise value per element of self directly into the caller's noise tensor. Nothing used to check that noise had the same shape as self or was contiguous, so a non-contiguous noise (e.g. an expanded view) was silently written through a discarded contiguous temporary: the returned output was correct but the caller's noise kept -its old values. These tests pin the error checks added for both cases. +its old values. Non-contiguous noise now materializes, the same way the +output does. """ import torch @@ -39,14 +40,14 @@ def test_noise_non_contiguous(self): x = torch.randn(4, 8, device=xpu_device) noise = torch.randn(4, 16, device=xpu_device)[:, ::2] self.assertFalse(noise.is_contiguous()) - with self.assertRaisesRegex(RuntimeError, "Expected contiguous tensor"): - torch.rrelu_with_noise(x, noise, training=True) - - # An expanded view: same shape as x, but not a real buffer. - noise = torch.randn(1, 8, device=xpu_device).expand(4, 8) - self.assertFalse(noise.is_contiguous()) - with self.assertRaisesRegex(RuntimeError, "Expected contiguous tensor"): - torch.rrelu_with_noise(x, noise, training=True) + out = torch.rrelu_with_noise(x, noise, training=True) + self.assertEqual(out, x * noise) + + # An expanded view: its storage holds one row; writing through its + # data pointer used to go past the storage. It materializes now. + noise = torch.zeros(1, 8, device=xpu_device).expand(4, 8) + out = torch.rrelu_with_noise(x, noise, training=True) + self.assertEqual(out, x * noise) def test_noise_written(self): # Positive path: the kernel must write into the caller's noise. From 96215b1816a1ef18fd803f480e92e4f4f9f30b3e Mon Sep 17 00:00:00 2001 From: Yuanyuan Chen Date: Wed, 23 Sep 2026 09:46:45 +0800 Subject: [PATCH 5/9] Expect the expanded-noise copy back to be rejected --- test/regressions/test_rrelu_with_noise.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/regressions/test_rrelu_with_noise.py b/test/regressions/test_rrelu_with_noise.py index 0ed12b2acfb..559658f6eab 100644 --- a/test/regressions/test_rrelu_with_noise.py +++ b/test/regressions/test_rrelu_with_noise.py @@ -44,10 +44,13 @@ def test_noise_non_contiguous(self): self.assertEqual(out, x * noise) # An expanded view: its storage holds one row; writing through its - # data pointer used to go past the storage. It materializes now. + # data pointer used to go past the storage. Distinct per-element + # noise values cannot land there, so the copy back rejects it. noise = torch.zeros(1, 8, device=xpu_device).expand(4, 8) - out = torch.rrelu_with_noise(x, noise, training=True) - self.assertEqual(out, x * noise) + with self.assertRaisesRegex( + RuntimeError, "refers to a single memory location" + ): + torch.rrelu_with_noise(x, noise, training=True) def test_noise_written(self): # Positive path: the kernel must write into the caller's noise. From f0f3d39d41f66dcbd84e900791af6f5cfe8c738f Mon Sep 17 00:00:00 2001 From: Yuanyuan Chen Date: Wed, 23 Sep 2026 13:11:33 +0800 Subject: [PATCH 6/9] Reformat the regression test for the lint check --- test/regressions/test_rrelu_with_noise.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/regressions/test_rrelu_with_noise.py b/test/regressions/test_rrelu_with_noise.py index 559658f6eab..3a4b8784c3c 100644 --- a/test/regressions/test_rrelu_with_noise.py +++ b/test/regressions/test_rrelu_with_noise.py @@ -33,7 +33,9 @@ def test_noise_shape_mismatch(self): torch.empty(2, 8, device=xpu_device), torch.empty(4, 1, device=xpu_device), ]: - with self.assertRaisesRegex(RuntimeError, "noise tensor shape must match self tensor shape"): + with self.assertRaisesRegex( + RuntimeError, "noise tensor shape must match self tensor shape" + ): torch.rrelu_with_noise(x, noise, training=True) def test_noise_non_contiguous(self): @@ -47,9 +49,7 @@ def test_noise_non_contiguous(self): # data pointer used to go past the storage. Distinct per-element # noise values cannot land there, so the copy back rejects it. noise = torch.zeros(1, 8, device=xpu_device).expand(4, 8) - with self.assertRaisesRegex( - RuntimeError, "refers to a single memory location" - ): + with self.assertRaisesRegex(RuntimeError, "refers to a single memory location"): torch.rrelu_with_noise(x, noise, training=True) def test_noise_written(self): From 66d61190c187193207933f5ed585427f0a7c32fc Mon Sep 17 00:00:00 2001 From: Yuanyuan Chen Date: Wed, 23 Sep 2026 13:14:27 +0800 Subject: [PATCH 7/9] Check the drawn noise range only where x is non-positive --- test/regressions/test_rrelu_with_noise.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/regressions/test_rrelu_with_noise.py b/test/regressions/test_rrelu_with_noise.py index 3a4b8784c3c..0c4d6350d05 100644 --- a/test/regressions/test_rrelu_with_noise.py +++ b/test/regressions/test_rrelu_with_noise.py @@ -57,8 +57,10 @@ def test_noise_written(self): x = torch.randn(64, 64, device=xpu_device) noise = torch.zeros_like(x) out = torch.rrelu_with_noise(x, noise, 0.1, 0.9, training=True) - self.assertTrue(torch.all(noise[x > 0] == 1)) - self.assertTrue(torch.all((noise >= 0.1) & (noise <= 0.9))) + pos, neg = x > 0, x <= 0 + self.assertTrue(torch.all(noise[pos] == 1)) + self.assertTrue(torch.all(noise[neg] >= 0.1)) + self.assertTrue(torch.all(noise[neg] <= 0.9)) self.assertTrue(torch.all(out >= 0)) From 4de5d134869b8cac0baf5289a6d68e3d8d95db7c Mon Sep 17 00:00:00 2001 From: Yuanyuan Chen Date: Tue, 6 Oct 2026 08:36:12 +0800 Subject: [PATCH 8/9] Reject overlapping noise when copying the materialized buffer back Materializing non-contiguous noise introduced an unchecked copy_ back into the caller's tensor. An expanded noise aliases the same storage across elements, so the copy silently wrote distinct values through overlapping memory instead of failing as intended. --- src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp b/src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp index 2d79211df15..8519115636f 100644 --- a/src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp +++ b/src/ATen/native/xpu/sycl/RreluWithNoiseKernels.cpp @@ -9,6 +9,7 @@ */ #include +#include #include #include #include @@ -150,6 +151,7 @@ inline void _rrelu_with_noise_xpu_train( output.copy_(tmp_output); } if (!noise_.is_contiguous()) { + at::assert_no_internal_overlap(noise_); noise_.copy_(tmp_noise); } } From 9b5debf95c7d628257dd7822d4e2e851b93c950d Mon Sep 17 00:00:00 2001 From: Yuanyuan Chen Date: Tue, 13 Oct 2026 14:12:38 +0800 Subject: [PATCH 9/9] Call rrelu_with_noise through its nn binding in the regression test The op carries python_module: nn, so the binding lives at torch._C._nn.rrelu_with_noise and all three tests died at attribute lookup before reaching the device. The output assertion was wrong too: the kernel returns x * r for non-positive x, so `out >= 0` could not hold; compare against x * noise instead. --- test/regressions/test_rrelu_with_noise.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/regressions/test_rrelu_with_noise.py b/test/regressions/test_rrelu_with_noise.py index 0c4d6350d05..d48e49d8c26 100644 --- a/test/regressions/test_rrelu_with_noise.py +++ b/test/regressions/test_rrelu_with_noise.py @@ -36,13 +36,13 @@ def test_noise_shape_mismatch(self): with self.assertRaisesRegex( RuntimeError, "noise tensor shape must match self tensor shape" ): - torch.rrelu_with_noise(x, noise, training=True) + torch._C._nn.rrelu_with_noise(x, noise, training=True) def test_noise_non_contiguous(self): x = torch.randn(4, 8, device=xpu_device) noise = torch.randn(4, 16, device=xpu_device)[:, ::2] self.assertFalse(noise.is_contiguous()) - out = torch.rrelu_with_noise(x, noise, training=True) + out = torch._C._nn.rrelu_with_noise(x, noise, training=True) self.assertEqual(out, x * noise) # An expanded view: its storage holds one row; writing through its @@ -50,18 +50,18 @@ def test_noise_non_contiguous(self): # noise values cannot land there, so the copy back rejects it. noise = torch.zeros(1, 8, device=xpu_device).expand(4, 8) with self.assertRaisesRegex(RuntimeError, "refers to a single memory location"): - torch.rrelu_with_noise(x, noise, training=True) + torch._C._nn.rrelu_with_noise(x, noise, training=True) def test_noise_written(self): # Positive path: the kernel must write into the caller's noise. x = torch.randn(64, 64, device=xpu_device) noise = torch.zeros_like(x) - out = torch.rrelu_with_noise(x, noise, 0.1, 0.9, training=True) + out = torch._C._nn.rrelu_with_noise(x, noise, 0.1, 0.9, training=True) pos, neg = x > 0, x <= 0 self.assertTrue(torch.all(noise[pos] == 1)) self.assertTrue(torch.all(noise[neg] >= 0.1)) self.assertTrue(torch.all(noise[neg] <= 0.9)) - self.assertTrue(torch.all(out >= 0)) + self.assertEqual(out, x * noise) if __name__ == "__main__":