Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions cpp/src/svm/kernelcache.cuh
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,19 @@ class KernelCache {
cache_state = CacheState::WS_INITIALIZED;
}

/**
* @brief Finish processing a working set without updating the cache
*
* A block solve can legitimately produce no coefficient updates. In that
* case there is no full kernel tile to process, but the working-set state
* still needs to be closed before another working set can be initialized.
*/
void FinishWorkingSet()
{
ASSERT(cache_state == CacheState::WS_INITIALIZED, "Working set not initialized!");
cache_state = CacheState::READY;
}

/**
* @brief Retrieve kernel indices
*
Expand Down
17 changes: 15 additions & 2 deletions cpp/src/svm/smosolver.cuh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION.
* SPDX-FileCopyrightText: Copyright (c) 2019-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

Expand Down Expand Up @@ -177,7 +177,8 @@ void SmoSolver<math_t>::Solve(MatrixViewType matrix,
stream);
RAFT_CUDA_TRY(cudaPeekAtLastError());
// The following should be performed only for elements with nonzero delta_alpha
if (nnz_da > 0) {
bool made_progress = nnz_da > 0;
if (made_progress) {
auto batch_descriptor = cache.InitFullTileBatching(nz_da_idx.data(), nnz_da);

while (cache.getNextBatchKernel(batch_descriptor)) {
Expand All @@ -191,6 +192,8 @@ void SmoSolver<math_t>::Solve(MatrixViewType matrix,
batch_descriptor.kernel_data);
RAFT_CUDA_TRY(cudaPeekAtLastError());
}
} else {
cache.FinishWorkingSet();
}
handle.sync_stream(stream);
raft::common::nvtx::pop_range();
Expand All @@ -203,6 +206,16 @@ void SmoSolver<math_t>::Solve(MatrixViewType matrix,
if ((max_iter != -1 && n_iter >= max_iter) || n_outer_iter >= max_outer_iter) {
keep_going = false;
}
if (keep_going && !made_progress) {
const char* advice = std::is_same<math_t, float>::value
? " Try using float64 input or reducing the magnitude of the kernel "
"values."
: " Try rescaling the input data or adjusting the kernel parameters.";
THROW(
"SMO error: solver made no progress while the stopping criterion was not satisfied. "
"This can happen when kernel values are too large for the input precision.%s",
advice);
}

if (n_outer_iter % 500 == 0) {
CUML_LOG_DEBUG("SMO iteration %d, diff %lf", n_outer_iter, (double)diff);
Expand Down
30 changes: 29 additions & 1 deletion cpp/tests/sg/svc_test.cu
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,33 @@ TYPED_TEST_P(KernelCacheTest, EvalTest)
}
}

TYPED_TEST_P(KernelCacheTest, FinishWorkingSetTest)
{
KernelParams params{KernelType::LINEAR, 3, 1, 0};
auto dense_view =
raft::make_device_strided_matrix_view<TypeParam, int, raft::layout_f_contiguous>(
this->x_dev.data(), this->n_rows, this->n_cols, 0);
GramMatrixBase<TypeParam>* kernel = KernelFactory<TypeParam>::create(ML::matrix::to_cuvs(params));
KernelCache<TypeParam, raft::device_matrix_view<TypeParam, int, raft::layout_stride>> cache(
this->handle,
dense_view,
this->n_rows,
this->n_cols,
this->n_ws,
kernel,
static_cast<cuvs::distance::kernels::KernelType>(params.kernel),
0,
C_SVC);

cache.InitWorkingSet(this->ws_idx_dev.data());
cache.FinishWorkingSet();

// A new working set can be initialized after a solve with no coefficient updates.
cache.InitWorkingSet(this->ws_idx_dev.data());
cache.FinishWorkingSet();
delete kernel;
}

TYPED_TEST_P(KernelCacheTest, SvcCacheEvalTest)
{
KernelParams param{KernelType::LINEAR, 3, 1, 0};
Expand Down Expand Up @@ -490,7 +517,8 @@ TYPED_TEST_P(KernelCacheTest, SvrCacheEvalTest)
}
}

REGISTER_TYPED_TEST_CASE_P(KernelCacheTest, EvalTest, SvcCacheEvalTest, SvrCacheEvalTest);
REGISTER_TYPED_TEST_CASE_P(
KernelCacheTest, EvalTest, FinishWorkingSetTest, SvcCacheEvalTest, SvrCacheEvalTest);
INSTANTIATE_TYPED_TEST_CASE_P(My, KernelCacheTest, FloatTypes);

template <typename math_t>
Expand Down
8 changes: 8 additions & 0 deletions python/cuml/tests/test_svm.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,14 @@ def test_svr_skl_cmp_weighted():
compare_svr(cuSVR, sklSVR, X, y)


def test_svr_float32_numerical_stagnation_error():
X = np.arange(5, dtype=np.float32).reshape(-1, 1)
y = np.arange(5, dtype=np.float32)

with pytest.raises(RuntimeError, match="made no progress.*float64"):
cu_svm.SVR(kernel="poly", degree=10).fit(X, y)


@pytest.mark.parametrize("classifier", [True, False])
@pytest.mark.parametrize("train_dtype", [np.float32, np.float64])
@pytest.mark.parametrize("test_dtype", [np.float64, np.float32])
Expand Down
Loading