smosolver.h
Go to the documentation of this file.
1 /*
2  * SPDX-FileCopyrightText: Copyright (c) 2019-2025, NVIDIA CORPORATION.
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 
6 #pragma once
7 
8 #include <cuml/common/logger.hpp>
9 #include <cuml/svm/svm_model.h>
10 
11 #include <raft/core/handle.hpp>
12 
13 #include <rmm/device_scalar.hpp>
14 #include <rmm/device_uvector.hpp>
15 
16 #include <thrust/device_ptr.h>
17 
18 #include <cuvs/distance/distance.hpp>
19 #include <cuvs/distance/grammian.hpp>
20 
21 #include <cassert>
22 #include <chrono>
23 #include <cstdlib>
24 #include <iostream>
25 #include <limits>
26 #include <sstream>
27 #include <string>
28 #include <type_traits>
29 
30 namespace ML {
31 namespace SVM {
32 
58 template <typename math_t>
59 class SmoSolver {
60  public:
61  SmoSolver(const raft::handle_t& handle,
62  SvmParameter param,
64  cuvs::distance::kernels::GramMatrixBase<math_t>* kernel)
65  : handle(handle),
66  C(param.C),
67  tol(param.tol),
68  kernel(kernel),
69  kernel_type(kernel_type),
70  cache_size(param.cache_size),
71  nochange_steps(param.nochange_steps),
72  epsilon(param.epsilon),
73  svmType(param.svmType),
74  stream(handle.get_stream()),
75  return_buff(2, stream),
76  alpha(0, stream),
77  C_vec(0, stream),
78  delta_alpha(0, stream),
79  f(0, stream),
80  y_label(0, stream)
81  {
82  ML::default_logger().set_level(param.verbosity);
83  }
84 
85  void GetNonzeroDeltaAlpha(const math_t* vec,
86  int n_ws,
87  const int* idx,
88  math_t* nz_vec,
89  int* n_nz,
90  int* nz_idx,
91  cudaStream_t stream);
114  template <typename MatrixViewType>
115  void Solve(MatrixViewType matrix,
116  int n_rows,
117  int n_cols,
118  math_t* y,
119  const math_t* sample_weight,
120  math_t** dual_coefs,
121  int* n_support,
122  SupportStorage<math_t>* support_matrix,
123  int** idx,
124  math_t* b,
125  int max_iter = -1,
126  int max_outer_iter = -1,
127  int max_inner_iter = 10000);
128 
144  void UpdateF(math_t* f, int n_rows, const math_t* delta_alpha, int n_ws, const math_t* cacheTile);
145 
167  void Initialize(math_t** y, const math_t* sample_weight, int n_rows, int n_cols);
168 
169  void InitPenalty(math_t* C_vec, const math_t* sample_weight, int n_rows);
170 
183  void SvcInit(const math_t* y);
184 
218  void SvrInit(const math_t* yr, int n_rows, math_t* yc, math_t* f);
219 
220  int GetNIter() { return n_iter; };
221 
222  private:
223  const raft::handle_t& handle;
224  cudaStream_t stream;
225 
226  int n_rows = 0;
227  int n_cols = 0;
228  int n_ws = 0;
229  int n_train = 0;
230 
231  // Buffers for the domain [n_train]
232  rmm::device_uvector<math_t> alpha;
233  rmm::device_uvector<math_t> f;
234  rmm::device_uvector<math_t> y_label;
235 
236  rmm::device_uvector<math_t> C_vec;
237 
238  // Buffers for the working set [n_ws]
240  rmm::device_uvector<math_t> delta_alpha;
241 
242  // Buffers to return some parameters from the kernel (iteration number, and
243  // convergence information)
244  rmm::device_uvector<math_t> return_buff;
245  math_t host_return_buff[2];
246 
247  math_t C;
248  math_t tol;
249  math_t epsilon;
250 
251  cuvs::distance::kernels::GramMatrixBase<math_t>* kernel;
253  float cache_size;
254 
255  SvmType svmType;
256 
257  // Variables to track convergence of training
258  math_t diff_prev;
259  int n_small_diff;
260  int nochange_steps;
261  int n_increased_diff;
262  int n_outer_iter;
263  int n_iter;
264  bool report_increased_diff;
265 
266  bool CheckStoppingCondition(math_t diff)
267  {
268  if (diff > diff_prev * 1.5 && n_outer_iter > 0) {
269  // Ideally, diff should decrease monotonically. In practice we can have
270  // small fluctuations (10% increase is not uncommon). Here we consider a
271  // 50% increase in the diff value large enough to indicate a problem.
272  // The 50% value is an educated guess that triggers the convergence debug
273  // message for problematic use cases while avoids false alarms in many
274  // other cases.
275  n_increased_diff++;
276  }
277  if (report_increased_diff && n_outer_iter > 100 && n_increased_diff > n_outer_iter * 0.1) {
278  CUML_LOG_DEBUG(
279  "Solver is not converging monotonically. This might be caused by "
280  "insufficient normalization of the feature columns. In that case "
281  "MinMaxScaler((0,1)) could help. Alternatively, for nonlinear kernels, "
282  "you can try to increase the gamma parameter. To limit execution time, "
283  "you can also adjust the number of iterations using the max_iter "
284  "parameter.");
285  report_increased_diff = false;
286  }
287  bool keep_going = true;
288  if (abs(diff - diff_prev) < 0.001 * tol) {
289  n_small_diff++;
290  } else {
291  diff_prev = diff;
292  n_small_diff = 0;
293  }
294  if (n_small_diff > nochange_steps) {
295  CUML_LOG_ERROR(
296  "SMO error: Stopping due to unchanged diff over %d"
297  " consecutive steps",
298  nochange_steps);
299  keep_going = false;
300  }
301  if (diff < tol) keep_going = false;
302  if (isnan(diff)) {
303  std::string txt;
304  if (std::is_same<float, math_t>::value) {
305  txt +=
306  " This might be caused by floating point overflow. In such case using"
307  " fp64 could help. Alternatively, try gamma='scale' kernel"
308  " parameter.";
309  }
310  THROW("SMO error: NaN found during fitting.%s", txt.c_str());
311  }
312  return keep_going;
313  }
314 
316  int GetDefaultMaxIter(int n_train, int max_outer_iter)
317  {
318  if (max_outer_iter == -1) {
319  max_outer_iter = n_train < std::numeric_limits<int>::max() / 100
320  ? n_train * 100
322  max_outer_iter = max(100000, max_outer_iter);
323  }
324  // else we have user defined iteration count which we do not change
325  return max_outer_iter;
326  }
327 
328  void ResizeBuffers(int n_train, int n_cols)
329  {
330  // This needs to know n_train, therefore it can be only called during solve
331  alpha.resize(n_train, stream);
332  C_vec.resize(n_train, stream);
333  f.resize(n_train, stream);
334  delta_alpha.resize(n_ws, stream);
335  if (svmType == EPSILON_SVR) y_label.resize(n_train, stream);
336  }
337 
338  void ReleaseBuffers()
339  {
340  alpha.release();
341  delta_alpha.release();
342  f.release();
343  y_label.release();
344  }
345 };
346 
347 }; // end namespace SVM
348 }; // end namespace ML
Solve the quadratic optimization problem using two level decomposition and Sequential Minimal Optimiz...
Definition: smosolver.h:59
int GetNIter()
Definition: smosolver.h:220
void SvrInit(const math_t *yr, int n_rows, math_t *yc, math_t *f)
Initializes the solver for epsilon-SVR.
void UpdateF(math_t *f, int n_rows, const math_t *delta_alpha, int n_ws, const math_t *cacheTile)
Update the f vector after a block solve step.
void Initialize(math_t **y, const math_t *sample_weight, int n_rows, int n_cols)
Initialize the problem to solve.
void SvcInit(const math_t *y)
Initialize Support Vector Classification.
void GetNonzeroDeltaAlpha(const math_t *vec, int n_ws, const int *idx, math_t *nz_vec, int *n_nz, int *nz_idx, cudaStream_t stream)
void InitPenalty(math_t *C_vec, const math_t *sample_weight, int n_rows)
void Solve(MatrixViewType matrix, int n_rows, int n_cols, math_t *y, const math_t *sample_weight, math_t **dual_coefs, int *n_support, SupportStorage< math_t > *support_matrix, int **idx, math_t *b, int max_iter=-1, int max_outer_iter=-1, int max_inner_iter=10000)
Solve the quadratic optimization problem.
SmoSolver(const raft::handle_t &handle, SvmParameter param, cuvs::distance::kernels::KernelType kernel_type, cuvs::distance::kernels::GramMatrixBase< math_t > *kernel)
Definition: smosolver.h:61
SvmType
Definition: svm_parameter.h:12
@ EPSILON_SVR
Definition: svm_parameter.h:12
math_t max(math_t a, math_t b)
Definition: learning_rate.h:16
KernelType
Definition: kernel_params.hpp:16
Definition: dbscan.hpp:18
rapids_logger::logger & default_logger()
Get the default logger.
Definition: logger.hpp:43
Definition: svm_model.h:12
Definition: svm_parameter.h:27
rapids_logger::level_enum verbosity
Print information about training.
Definition: svm_parameter.h:34