smosolver.h
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2019-2025, NVIDIA CORPORATION.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #pragma once
18 
19 #include <cuml/common/logger.hpp>
20 #include <cuml/svm/svm_model.h>
21 
22 #include <raft/core/handle.hpp>
23 
24 #include <rmm/device_scalar.hpp>
25 #include <rmm/device_uvector.hpp>
26 
27 #include <thrust/device_ptr.h>
28 
29 #include <cuvs/distance/distance.hpp>
30 #include <cuvs/distance/grammian.hpp>
31 
32 #include <cassert>
33 #include <chrono>
34 #include <cstdlib>
35 #include <iostream>
36 #include <limits>
37 #include <sstream>
38 #include <string>
39 #include <type_traits>
40 
41 namespace ML {
42 namespace SVM {
43 
69 template <typename math_t>
70 class SmoSolver {
71  public:
72  SmoSolver(const raft::handle_t& handle,
73  SvmParameter param,
75  cuvs::distance::kernels::GramMatrixBase<math_t>* kernel)
76  : handle(handle),
77  C(param.C),
78  tol(param.tol),
79  kernel(kernel),
80  kernel_type(kernel_type),
81  cache_size(param.cache_size),
82  nochange_steps(param.nochange_steps),
83  epsilon(param.epsilon),
84  svmType(param.svmType),
85  stream(handle.get_stream()),
86  return_buff(2, stream),
87  alpha(0, stream),
88  C_vec(0, stream),
89  delta_alpha(0, stream),
90  f(0, stream),
91  y_label(0, stream)
92  {
93  ML::default_logger().set_level(param.verbosity);
94  }
95 
96  void GetNonzeroDeltaAlpha(const math_t* vec,
97  int n_ws,
98  const int* idx,
99  math_t* nz_vec,
100  int* n_nz,
101  int* nz_idx,
102  cudaStream_t stream);
124  template <typename MatrixViewType>
125  void Solve(MatrixViewType matrix,
126  int n_rows,
127  int n_cols,
128  math_t* y,
129  const math_t* sample_weight,
130  math_t** dual_coefs,
131  int* n_support,
132  SupportStorage<math_t>* support_matrix,
133  int** idx,
134  math_t* b,
135  int max_outer_iter = -1,
136  int max_inner_iter = 10000);
137 
153  void UpdateF(math_t* f, int n_rows, const math_t* delta_alpha, int n_ws, const math_t* cacheTile);
154 
176  void Initialize(math_t** y, const math_t* sample_weight, int n_rows, int n_cols);
177 
178  void InitPenalty(math_t* C_vec, const math_t* sample_weight, int n_rows);
179 
192  void SvcInit(const math_t* y);
193 
227  void SvrInit(const math_t* yr, int n_rows, math_t* yc, math_t* f);
228 
229  private:
230  const raft::handle_t& handle;
231  cudaStream_t stream;
232 
233  int n_rows = 0;
234  int n_cols = 0;
235  int n_ws = 0;
236  int n_train = 0;
237 
238  // Buffers for the domain [n_train]
239  rmm::device_uvector<math_t> alpha;
240  rmm::device_uvector<math_t> f;
241  rmm::device_uvector<math_t> y_label;
242 
243  rmm::device_uvector<math_t> C_vec;
244 
245  // Buffers for the working set [n_ws]
247  rmm::device_uvector<math_t> delta_alpha;
248 
249  // Buffers to return some parameters from the kernel (iteration number, and
250  // convergence information)
251  rmm::device_uvector<math_t> return_buff;
252  math_t host_return_buff[2];
253 
254  math_t C;
255  math_t tol;
256  math_t epsilon;
257 
258  cuvs::distance::kernels::GramMatrixBase<math_t>* kernel;
260  float cache_size;
261 
262  SvmType svmType;
263 
264  // Variables to track convergence of training
265  math_t diff_prev;
266  int n_small_diff;
267  int nochange_steps;
268  int n_increased_diff;
269  int n_iter;
270  bool report_increased_diff;
271 
272  bool CheckStoppingCondition(math_t diff)
273  {
274  if (diff > diff_prev * 1.5 && n_iter > 0) {
275  // Ideally, diff should decrease monotonically. In practice we can have
276  // small fluctuations (10% increase is not uncommon). Here we consider a
277  // 50% increase in the diff value large enough to indicate a problem.
278  // The 50% value is an educated guess that triggers the convergence debug
279  // message for problematic use cases while avoids false alarms in many
280  // other cases.
281  n_increased_diff++;
282  }
283  if (report_increased_diff && n_iter > 100 && n_increased_diff > n_iter * 0.1) {
284  CUML_LOG_DEBUG(
285  "Solver is not converging monotonically. This might be caused by "
286  "insufficient normalization of the feature columns. In that case "
287  "MinMaxScaler((0,1)) could help. Alternatively, for nonlinear kernels, "
288  "you can try to increase the gamma parameter. To limit execution time, "
289  "you can also adjust the number of iterations using the max_iter "
290  "parameter.");
291  report_increased_diff = false;
292  }
293  bool keep_going = true;
294  if (abs(diff - diff_prev) < 0.001 * tol) {
295  n_small_diff++;
296  } else {
297  diff_prev = diff;
298  n_small_diff = 0;
299  }
300  if (n_small_diff > nochange_steps) {
301  CUML_LOG_ERROR(
302  "SMO error: Stopping due to unchanged diff over %d"
303  " consecutive steps",
304  nochange_steps);
305  keep_going = false;
306  }
307  if (diff < tol) keep_going = false;
308  if (isnan(diff)) {
309  std::string txt;
310  if (std::is_same<float, math_t>::value) {
311  txt +=
312  " This might be caused by floating point overflow. In such case using"
313  " fp64 could help. Alternatively, try gamma='scale' kernel"
314  " parameter.";
315  }
316  THROW("SMO error: NaN found during fitting.%s", txt.c_str());
317  }
318  return keep_going;
319  }
320 
322  int GetDefaultMaxIter(int n_train, int max_outer_iter)
323  {
324  if (max_outer_iter == -1) {
325  max_outer_iter = n_train < std::numeric_limits<int>::max() / 100
326  ? n_train * 100
328  max_outer_iter = max(100000, max_outer_iter);
329  }
330  // else we have user defined iteration count which we do not change
331  return max_outer_iter;
332  }
333 
334  void ResizeBuffers(int n_train, int n_cols)
335  {
336  // This needs to know n_train, therefore it can be only called during solve
337  alpha.resize(n_train, stream);
338  C_vec.resize(n_train, stream);
339  f.resize(n_train, stream);
340  delta_alpha.resize(n_ws, stream);
341  if (svmType == EPSILON_SVR) y_label.resize(n_train, stream);
342  }
343 
344  void ReleaseBuffers()
345  {
346  alpha.release();
347  delta_alpha.release();
348  f.release();
349  y_label.release();
350  }
351 };
352 
353 }; // end namespace SVM
354 }; // end namespace ML
Solve the quadratic optimization problem using two level decomposition and Sequential Minimal Optimiz...
Definition: smosolver.h:70
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 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_outer_iter=-1, int max_inner_iter=10000)
Solve the quadratic optimization problem.
void InitPenalty(math_t *C_vec, const math_t *sample_weight, int n_rows)
SmoSolver(const raft::handle_t &handle, SvmParameter param, cuvs::distance::kernels::KernelType kernel_type, cuvs::distance::kernels::GramMatrixBase< math_t > *kernel)
Definition: smosolver.h:72
SvmType
Definition: svm_parameter.h:23
@ EPSILON_SVR
Definition: svm_parameter.h:23
math_t max(math_t a, math_t b)
Definition: learning_rate.h:27
KernelType
Definition: kernel_params.hpp:27
Definition: dbscan.hpp:29
rapids_logger::logger & default_logger()
Get the default logger.
Definition: logger.hpp:54
Definition: svm_model.h:23
Definition: svm_parameter.h:36
rapids_logger::level_enum verbosity
Print information about training.
Definition: svm_parameter.h:44