From edccc6b234687976861209b450270a61dbb487dd Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Tue, 4 Aug 2026 00:59:52 +0200 Subject: [PATCH 1/5] Validate precomputed pairwise kernel shapes --- python/cuml/cuml/metrics/pairwise_kernels.py | 12 +++++++--- python/cuml/tests/test_kernel_ridge.py | 25 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/python/cuml/cuml/metrics/pairwise_kernels.py b/python/cuml/cuml/metrics/pairwise_kernels.py index 082fef96e8..a88336ce77 100644 --- a/python/cuml/cuml/metrics/pairwise_kernels.py +++ b/python/cuml/cuml/metrics/pairwise_kernels.py @@ -283,12 +283,18 @@ def pairwise_kernels( Y = X else: Y = check_array(Y, input_name="Y") - if X.shape[1] != Y.shape[1]: - raise ValueError("X and Y have different dimensions.") - if metric == "precomputed": + if X.shape[1] != Y.shape[0]: + raise ValueError( + "Precomputed metric requires shape " + "(n_queries, n_indexed). " + f"Got {X.shape} for {Y.shape[0]} indexed." + ) return X + if X.shape[1] != Y.shape[1]: + raise ValueError("X and Y have different dimensions.") + if metric in PAIRWISE_KERNEL_FUNCTIONS: kwds = _filter_params( PAIRWISE_KERNEL_FUNCTIONS[metric], filter_params, **kwds diff --git a/python/cuml/tests/test_kernel_ridge.py b/python/cuml/tests/test_kernel_ridge.py index 2fcff05e09..cc0714d983 100644 --- a/python/cuml/tests/test_kernel_ridge.py +++ b/python/cuml/tests/test_kernel_ridge.py @@ -107,6 +107,31 @@ def bad_numba_kernel2(x, y, z): assert np.allclose(X, pairwise_kernels(X, metric="precomputed")) +@pytest.mark.parametrize("shape", [(5, 3), (3, 5)]) +def test_pairwise_kernels_precomputed_requires_square(shape): + X = np.ones(shape) + + with pytest.raises(ValueError, match="Precomputed metric requires shape"): + pairwise_kernels(X, metric="precomputed") + + +def test_pairwise_kernels_precomputed_cross_kernel(): + X = np.arange(6).reshape(3, 2) + Y = np.ones((2, 10)) + + result = pairwise_kernels(X, Y, metric="precomputed") + + cp.testing.assert_array_equal(result, cp.asarray(X)) + + +def test_pairwise_kernels_precomputed_wrong_indexed_count(): + X = np.ones((3, 4)) + Y = np.ones((2, 10)) + + with pytest.raises(ValueError, match="Precomputed metric requires shape"): + pairwise_kernels(X, Y, metric="precomputed") + + @cuda.jit(device=True) def custom_kernel(x, y, custom_arg=5.0): sum = 0.0 From 11b250e1bf255582ab14ad0a91a3c2dbca87d1f3 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Tue, 4 Aug 2026 01:06:08 +0200 Subject: [PATCH 2/5] Document precomputed kernel shape validation --- python/cuml/cuml/metrics/pairwise_kernels.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/python/cuml/cuml/metrics/pairwise_kernels.py b/python/cuml/cuml/metrics/pairwise_kernels.py index a88336ce77..2f92c72b8f 100644 --- a/python/cuml/cuml/metrics/pairwise_kernels.py +++ b/python/cuml/cuml/metrics/pairwise_kernels.py @@ -206,12 +206,18 @@ def pairwise_kernels( (n_samples_X, n_features) Array of pairwise kernels between samples, or a feature array. The shape of the array should be (n_samples_X, n_samples_X) if - metric == "precomputed" and (n_samples_X, n_features) otherwise. + metric == "precomputed" and Y is None. If Y is provided with + metric == "precomputed", X should have shape (n_queries, n_indexed), + where n_indexed must equal Y.shape[0]. Otherwise, X should have shape + (n_samples_X, n_features). Acceptable formats: cuDF DataFrame, NumPy ndarray, Numba device ndarray, cuda array interface compliant array like CuPy. Y : array-like (device or host) of shape (n_samples_Y, n_features), \ default=None A second feature array only if X has shape (n_samples_X, n_features). + For metric == "precomputed", only Y.shape[0] is used to validate the + shape of X; the values and second dimension of Y do not affect the + returned matrix. Acceptable formats: cuDF DataFrame, NumPy ndarray, Numba device ndarray, cuda array interface compliant array like CuPy. metric : str or callable (numba device function), default="linear" @@ -245,7 +251,9 @@ def pairwise_kernels( Notes ----- - If metric is 'precomputed', Y is ignored and X is returned. + If metric is 'precomputed', Y is only used to validate the shape of X: + X must be square when Y is None, or X.shape[1] must equal Y.shape[0] + otherwise. X is returned unchanged. Examples -------- From 52104bb9442889f79dacf4ad7b2201175cd1769f Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Tue, 4 Aug 2026 01:10:56 +0200 Subject: [PATCH 3/5] Clarify precomputed kernel Y documentation --- python/cuml/cuml/metrics/pairwise_kernels.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/python/cuml/cuml/metrics/pairwise_kernels.py b/python/cuml/cuml/metrics/pairwise_kernels.py index 2f92c72b8f..e5f635cc39 100644 --- a/python/cuml/cuml/metrics/pairwise_kernels.py +++ b/python/cuml/cuml/metrics/pairwise_kernels.py @@ -214,10 +214,11 @@ def pairwise_kernels( ndarray, cuda array interface compliant array like CuPy. Y : array-like (device or host) of shape (n_samples_Y, n_features), \ default=None - A second feature array only if X has shape (n_samples_X, n_features). - For metric == "precomputed", only Y.shape[0] is used to validate the - shape of X; the values and second dimension of Y do not affect the - returned matrix. + For metrics other than "precomputed", a second feature array only if X + has shape (n_samples_X, n_features). For metric == "precomputed", Y + can be any 2D array; only Y.shape[0] is used to validate the shape of X, + and the values and second dimension of Y do not affect the returned + matrix. Acceptable formats: cuDF DataFrame, NumPy ndarray, Numba device ndarray, cuda array interface compliant array like CuPy. metric : str or callable (numba device function), default="linear" From fc9b3ac92c3a4afbe9c62a94979dd93d9f2f363d Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Tue, 4 Aug 2026 16:09:02 +0200 Subject: [PATCH 4/5] Add feature dimension regression test --- python/cuml/tests/test_kernel_ridge.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/python/cuml/tests/test_kernel_ridge.py b/python/cuml/tests/test_kernel_ridge.py index cc0714d983..5d599bdffc 100644 --- a/python/cuml/tests/test_kernel_ridge.py +++ b/python/cuml/tests/test_kernel_ridge.py @@ -132,6 +132,14 @@ def test_pairwise_kernels_precomputed_wrong_indexed_count(): pairwise_kernels(X, Y, metric="precomputed") +def test_pairwise_kernels_rejects_mismatched_feature_dimensions(): + X = np.ones((3, 4)) + Y = np.ones((2, 5)) + + with pytest.raises(ValueError, match="X and Y have different dimensions."): + pairwise_kernels(X, Y, metric="linear") + + @cuda.jit(device=True) def custom_kernel(x, y, custom_arg=5.0): sum = 0.0 From a1c4d90ecacb851413f0b3f9fd86d76b59007953 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Tue, 4 Aug 2026 16:15:20 +0200 Subject: [PATCH 5/5] Escape kernel dimension test regex --- python/cuml/tests/test_kernel_ridge.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/cuml/tests/test_kernel_ridge.py b/python/cuml/tests/test_kernel_ridge.py index 5d599bdffc..0644bb179c 100644 --- a/python/cuml/tests/test_kernel_ridge.py +++ b/python/cuml/tests/test_kernel_ridge.py @@ -136,7 +136,9 @@ def test_pairwise_kernels_rejects_mismatched_feature_dimensions(): X = np.ones((3, 4)) Y = np.ones((2, 5)) - with pytest.raises(ValueError, match="X and Y have different dimensions."): + with pytest.raises( + ValueError, match=r"X and Y have different dimensions\." + ): pairwise_kernels(X, Y, metric="linear")