# AI Code Review Guidelines - cuML C++/CUDA
**Role**: Act as a principal engineer with 10+ years experience in GPU computing and high-performance numerical computing. Focus ONLY on CRITICAL and HIGH issues.
**Target**: Sub-3% false positive rate. Be direct, concise, minimal.
**Context**: cuML C++ layer provides GPU-accelerated ML algorithm implementations using CUDA, with dependencies on RAFT, RMM, cuVS, libcudacxx, thrust, and CUB.
## IGNORE These Issues
- Style/formatting (clang-format handles this)
- Minor naming preferences (unless truly misleading)
- Personal taste on implementation (unless impacts maintainability)
- Nits that don't affect functionality
- Already-covered issues (one comment per root cause)
## CRITICAL Issues (Always Comment)
### GPU/CUDA Errors
- Unchecked CUDA errors (kernel launches, memory operations, synchronization)
- Race conditions in GPU kernels (shared memory, atomics, warps)
- Device memory leaks (cudaMalloc/cudaFree imbalance, leaked streams/events)
- Invalid memory access (out-of-bounds, use-after-free, host/device confusion)
- Missing CUDA synchronization causing non-deterministic failures
- Kernel launch with zero blocks/threads or invalid grid/block dimensions
- **Host-side integer overflow/underflow in size, launch-dim, or host-index arithmetic** (see "Integer arithmetic for sizes, launches, and host indexing" below)
- **Missing explicit stream creation for concurrent operations** (reusing default stream, missing stream isolation)
- **Incorrect stream lifecycle management** (using destroyed streams, not creating dedicated streams for concurrent ops)
### Algorithm Correctness
- Logic errors in ML algorithm kernels (clustering, regression, classification, dimensionality reduction)
- Incorrect distance metrics, kernels, or loss function implementations
- Numerical instability causing wrong results (overflow, underflow, precision loss)
- Incorrect gradient computations or convergence criteria
- **Data layout bugs** (incorrect row-major vs column-major assumptions)
### Resource Management
- GPU memory leaks (device allocations, managed memory, pinned memory)
- CUDA stream/event leaks or improper cleanup
- Missing RAII or proper cleanup. Including in exception paths.
- Resource exhaustion (GPU memory)
### API Breaking Changes
- C++ API changes without proper deprecation warnings
- Changes to data structures exposed in public headers (`cpp/include/cuml/`)
- Breaking changes to algorithm behavior
## HIGH Issues (Comment if Substantial)
### Performance Issues
- Inefficient GPU kernel launches (low occupancy, poor memory access patterns)
- Unnecessary host-device synchronization blocking GPU pipeline
- Suboptimal memory access patterns (non-coalesced, strided, unaligned)
- Excessive memory allocations in hot paths
- Warp divergence in compute-heavy kernels
- Shared memory bank conflicts
### Numerical Stability
- Floating-point operations prone to catastrophic cancellation
- Missing checks for division by zero or near-zero values
- Ill-conditioned matrix operations without preconditioning
- Accumulation errors in iterative algorithms
- Unsafe casting between numeric types (doubleβfloat with potential precision loss)
- Missing epsilon comparisons for floating-point equality checks
- **Numerical edge cases** (near-zero eigenvalues, degenerate matrices, extreme values)
### Concurrency & Thread Safety
- Race conditions in multi-GPU operations
- Improper CUDA stream management causing false dependencies
- Deadlock potential in resource acquisition
- Thread-unsafe use of global/static variables
- **Concurrent operations sharing streams incorrectly** (multi-GPU without proper isolation)
- **Stream reuse across independent operations** (causing unwanted serialization or race conditions)
### Design & Architecture
- Hard-coded GPU device IDs or resource limits
- Inappropriate use of exceptions in performance-critical paths
- Significant code duplication (3+ occurrences). Including in kernel logic.
- Reinventing functionality already available in RAFT, RMM, cuVS, libcudacxx, thrust, or CUB
### Test Quality
- Missing validation of numerical correctness
- **Using external datasets** (tests must not depend on external resources; use synthetic data or bundled datasets)
## MEDIUM Issues (Comment Selectively)
- Missing input validation (negative dimensions, null pointers)
- Deprecated CUDA API usage
- **Unclear data format in function parameters** (ambiguous row-major or column-major)
## Review Protocol
1. **CUDA correctness**: Errors checked? Memory safety? Race conditions? Synchronization?
2. **Algorithm correctness**: Does the kernel logic produce correct results? Numerical stability?
3. **Resource management**: GPU memory leaks? Stream/event cleanup?
4. **Performance**: GPU bottlenecks? Unnecessary sync? Memory access patterns?
5. **API stability**: Breaking changes to C++ APIs?
6. **Data layout**: Row/column major handled correctly?
7. **Stream lifecycle**: Are CUDA streams explicitly created/destroyed for concurrent operations?
8. **Ask, don't tell**: "Have you considered X?" not "You should do X"
## Quality Threshold
Before commenting, ask:
1. Is this actually wrong/risky, or just different?
2. Would this cause a real problem (crash, wrong results, leak)?
3. Does this comment add unique value?
**If no to any: Skip the comment.**
## Output Format
- Use severity labels: CRITICAL, HIGH, MEDIUM
- Be concise: One-line issue summary + one-line impact
- Provide code suggestions when you have concrete fixes
- No preamble or sign-off
## Examples to Follow
**CRITICAL** (GPU memory leak):
```
CRITICAL: GPU memory leak in fit()
Issue: Device memory allocated but never freed on error path
Why: Causes GPU OOM on repeated calls
Suggested fix:
if (cudaMalloc(&d_data, size) != cudaSuccess) {
cudaFree(d_centroids);
return ERROR_CODE;
}
```
**CRITICAL** (unchecked CUDA error):
```
CRITICAL: Unchecked kernel launch
Issue: Kernel launch error not checked
Why: Subsequent operations assume success, causing silent corruption
Suggested fix:
myKernel<<<grid, block>>>(args);
RAFT_CUDA_TRY(cudaGetLastError());
```
**HIGH** (numerical stability):
```
HIGH: Potential division by near-zero
Issue: No epsilon check before division in distance computation
Why: Can produce Inf/NaN values corrupting results
Consider: Add epsilon threshold check or use safe division helper
```
**HIGH** (performance issue):
```
HIGH: Unnecessary synchronization in hot path
Issue: cudaDeviceSynchronize() inside iteration loop
Why: Blocks GPU pipeline, 10x slowdown on benchmarks
Consider: Move sync outside loop or use streams with events
```
**CRITICAL** (data layout mismatch):
```
CRITICAL: Incorrect memory layout assumption in kernel
Issue: Kernel assumes row-major data but input is column-major
Why: Memory access pattern produces wrong results
Impact: Silent data corruption
Suggested fix:
// Check and handle data layout explicitly
if (input.is_column_major()) {
// Use column-major kernel variant
}
```
**HIGH** (missing stream isolation):
```
HIGH: Multi-GPU operation missing dedicated streams
Issue: Multi-GPU operation uses default stream without per-device streams
Why: Can cause serialization across devices, race conditions, or deadlocks
Suggested fix:
cudaStream_t per_device_stream;
cudaStreamCreate(&per_device_stream);
// Use per_device_stream for this GPU's operations
// cudaStreamDestroy(per_device_stream) in cleanup
```
## Examples to Avoid
**Boilerplate** (avoid):
- "CUDA Best Practices: Using streams improves concurrency..."
- "Memory Management: Proper cleanup of GPU resources is important..."
**Subjective style** (ignore):
- "Consider using auto here instead of explicit type"
- "This function could be split into smaller functions"
---
## C++/CUDA-Specific Considerations
**Error Handling**:
- Use RAFT macros: `RAFT_CUDA_TRY`, `RAFT_CUBLAS_TRY`, `RAFT_CUSOLVER_TRY`
- Every CUDA call must have error checking (kernel launches, memory ops, sync)
- Use `RAFT_CUDA_TRY_NO_THROW` in destructors
**Memory Management**:
- Use RMM for device memory allocations where possible
- Use `raft::handle_t` for stream and allocator management
- Prefer RAII patterns (`rmm::device_uvector`, `rmm::device_buffer`)
**Stream Management**:
- Get streams from `raft::handle_t::get_stream()`
- For multi-stream operations, use `handle.get_internal_stream(idx)`
- Concurrent operations (multi-GPU, async ops) must have dedicated streams
- Clearly document stream lifecycle (who creates, who destroys)
**Threading**:
- Only OpenMP is allowed for host threading
- Algorithms should be thread-safe with different `raft::handle_t` instances
- Use `raft::stream_syncer` for proper stream ordering
**Public API** (`cpp/include/cuml/`):
- Functions must be stateless (POD types, `raft::handle_t`, pointers to POD)
- Doxygen documentation required for all public functions
- API changes require deprecation warnings
---
## Common Bug Patterns
### 1. Memory Layout Confusion
**Pattern**: Incorrect row-major vs column-major assumptions
**Red flags**:
- Direct pointer access without verifying data layout
- Kernel assuming row-major when data might be column-major
- Missing layout parameter in function signatures
### 2. CUDA Stream Lifecycle Issues
**Pattern**: Missing explicit stream creation for concurrent operations
**Red flags**:
- Multi-GPU operations without dedicated stream per device
- Stream creation inside loop but destruction outside loop
- Using `nullptr` or default stream for operations that need isolation
- Missing `cudaStreamDestroy` for explicitly created streams
### 3. GPU Memory Leaks
**Pattern**: Device memory allocated but not properly freed
**Red flags**:
- cudaMalloc without corresponding cudaFree
- Temporary GPU buffers allocated per iteration without cleanup
- Exception paths skipping memory cleanup
- Missing RAII or smart pointers for GPU memory
### 4. Numerical Instability in Kernels
**Pattern**: Incorrect floating-point handling in distance/kernel computations
**Red flags**:
- Division without epsilon check
- Not handling zero-norm vectors
- Accumulation without compensation (Kahan summation)
- Unsafe type casting (doubleβfloat)
### 5. Integer arithmetic for sizes, launches, and host indexing
**Pattern**: Host-side `int` (or any sub-`size_t`) arithmetic flows into an
allocation size, a kernel launch dimension, or a host pointer offset. The
product silently overflows (CWE-190) or a subtraction underflows (CWE-191)
before being widened to `size_t`, producing an undersized allocation (then GPU
heap overflow), a near-`SIZE_MAX` allocation (OOM crash), or an invalid launch
config.
**Scope**: Host code only. Device/kernel arithmetic is intentionally out of
scope β checks belong at the host-side computation site so kernels stay
branch-free.
**Required helpers** (host-side, negligible cost), declared in
`cpp/include/cuml/common/checked_arithmetic.hpp` under namespace `ML`:
- `ML::checked_mul<size_t>(a, b, ...)` β variadic, `RAFT_FAIL` on overflow.
- `ML::checked_add<size_t>(a, b, ...)` β variadic, `RAFT_FAIL` on overflow.
- `ML::checked_sub<size_t>(a, b)` β `RAFT_FAIL` on underflow.
- `ML::checked_div<size_t>(a, b)` β `RAFT_FAIL` on `b == 0` (and on signed
`INT_MIN / -1` overflow when used with signed types).
- `ML::narrow_cast<int>(value)` β `RAFT_FAIL` if `value` does not fit in the
target type. Use at sites where an existing API forces narrowing (e.g.
passing a `size_t` size to a function that takes `int`, or storing
`pair::first` into an `int`). Preserves the cast but ensures it doesn't
silently corrupt the value. A true widening (target strictly wider than
source) skips the magnitude check, but a negative source into an unsigned
target still traps β sign loss is always an error.
- `ML::cuda_launch_t` β alias for the integer type expected by CUDA launch
configuration (`dim3` components, shared-mem size). Use
`ML::narrow_cast<ML::cuda_launch_t>(...)` for values destined for a `<<<>>>`
grid/block dimension so the call site is self-documenting and the trap
happens host-side instead of as a silent narrow at the launch syntax.
Use them anywhere a count-product, count-sum, count-difference, or
count-quotient is passed to an allocator, a `dim3`, a span constructor, or a
`size_t`/`int64_t` parameter. Public estimator entry points that accept
count-like `int`s should validate shape preconditions (including ordering,
e.g. `n_obs > d + s*D`) before any allocation or launch.
**Red flags**:
- `int * int` / `int + int` / `int - int` / `int / int` passed to
`rmm::device_uvector` ctor/`.resize(...)`, `cudaMalloc*`,
`*allocator*.allocate(...)`, `dim3(...)`, `raft::span` / `cuda::std::span`
construction, or any `size_t` parameter.
- Subtraction of integer counts used as a length without a prior
`RAFT_EXPECTS` / `RAFT_FAIL` guard or `checked_sub`.
- Division (including ceil-div like `(n + b - 1) / b` for launch dims) where
the divisor is derived from a parameter and not statically known to be
non-zero β use `checked_div` or guard explicitly.
- Cumulative offsets built by repeatedly adding `int` counts without
`checked_add` β `offset + count` can overflow before it's used to index.
- Silent narrowing: `int n = c.size();`, `int n = m_shape.first;`,
`foo(some_size_t)` where `foo` takes `int`, and similar β any implicit
conversion from a wider integer type to a narrower one that involves a
count, index, or dimension. Replace with `ML::narrow_cast<int>(...)` (or
widen the receiver / API).
- A size guard that casts to `size_t` but the matching allocation a few lines
down does not (the guard validates a value the allocator never sees).
- Public C/Cython entry points taking count-like `int` parameters with no
upper-bound validation before downstream allocation.
- Grid/block dimension arithmetic in `int` where the product can plausibly
exceed 2^31 on large inputs.
- A value computed in `int` (or `size_t`) that is passed directly into a
`<<<grid, block, ...>>>` launch and relies on the implicit conversion to
`unsigned int`. Require `ML::narrow_cast<ML::cuda_launch_t>(...)` so the
conversion is checked and the call site is explicit.
---
## Code Review Checklists
### When Reviewing CUDA Kernels
- [ ] Are CUDA errors checked after kernel launch (with peek)?
- [ ] Is shared memory usage within limits and avoiding bank conflicts?
- [ ] Is shared memory used when clearly possible?
- [ ] Is thread synchronization done correctly? Are any __syncthreads call unnecessary, misplaced or missing?
- [ ] Is memory access coalesced?
- [ ] Is memory aligned?
- [ ] Is there serial work inside of a thread?
- [ ] Are warp divergence issues minimized?
- [ ] Are grid/block dimensions validated?
### When Reviewing Multi-GPU Operations
- [ ] Is stream lifecycle clearly documented?
- [ ] Are independent GPU operations using dedicated streams?
- [ ] Is `cudaSetDevice` called before device-specific operations?
- [ ] Are stream errors checked?
### When Reviewing Memory Operations
- [ ] Is data layout (row-major vs column-major) explicitly handled?
- [ ] Are device allocations paired with deallocations?
- [ ] Is RAII used for GPU resources?
- [ ] Are exception paths cleaning up resources?
### When Reviewing Numerical Computations
- [ ] Are edge cases handled (zero-norm, identical points)?
- [ ] Are divisions protected against near-zero denominators?
- [ ] Are epsilon tolerances used for floating-point comparisons?
- [ ] Is numerical stability maintained (avoiding overflow/underflow)?
### When Reviewing Tests
- [ ] Are all datasets synthetic or bundled (no external resource dependencies)?
- [ ] Is numerical correctness validated?
- [ ] Are edge cases tested (empty, single element, extreme values)?
---
**Remember**: Focus on correctness and safety. Catch real bugs (crashes, wrong results, leaks), ignore style preferences. For cuML C++: CUDA correctness and numerical stability are paramount.
# AI Code Review Guidelines - cuML Python
**Role**: Act as a principal engineer with 10+ years experience in machine learning systems and Python API design. Focus ONLY on CRITICAL and HIGH issues.
**Target**: Sub-3% false positive rate. Be direct, concise, minimal.
**Context**: cuML Python layer provides scikit-learn compatible APIs for GPU-accelerated ML algorithms, supporting cuDF, pandas, and NumPy inputs.
## IGNORE These Issues
- Style/formatting (pre-commit hooks handle this)
- Minor naming preferences (unless truly misleading)
- Personal taste on implementation (unless impacts maintainability)
- Nits that don't affect functionality
- Already-covered issues (one comment per root cause)
## CRITICAL Issues (Always Comment)
### Scikit-learn Compatibility
- Function and parameter names or defaults differing from scikit-learn without justification
- Different behavior for edge cases (empty arrays, single sample) vs scikit-learn without justification
- Arbitrary violations of common estimator guidelines, especially critical ones like constructor state validation
- **Initializing fitted attributes in `__init__`** (e.g., `self.coef_ = None`) - only parameters should be set in constructor
### Algorithm Correctness
- Logic errors in ML algorithm implementations
- Incorrect distance metrics, kernels, or loss function implementations
- Numerical instability causing wrong results
- Breaking changes to algorithm behavior
- **Model parameter initialization errors** (incorrect weights, invalid starting values)
- **Algorithm state corruption** (incorrect state transitions between fit/predict/transform)
### API Breaking Changes
- Python API changes breaking backward compatibility
- Changes to public estimator interfaces
- Removing or renaming public methods/attributes without deprecation
- We usually require at least one release cycle for deprecations
### Input Handling Errors
- Incorrect handling of cuDF vs pandas vs NumPy inputs
- Silent data corruption from type coercion
- Missing validation causing crashes on invalid input
## HIGH Issues (Comment if Substantial)
### Model State Management
- fit() not clearing previous model state
- Reusing internal buffers without resetting between calls
- Missing initialization of model parameters before training
- Previous fit() state affecting new training
### Input Validation
- Missing dimension checks (n_samples, n_features)
- Not handling edge cases (empty datasets, single sample)
### Test Quality
- Missing validation of numerical correctness (only checking "runs without error")
- Missing edge case coverage (empty datasets, single sample, high-dimensional data)
- **Missing tests for fit/predict/transform consistency**
- **Missing comparison with scikit-learn** (verify API compatibility and numerical equivalence)
- Missing tests for different input types (cuDF, pandas, NumPy)
- **Using external datasets** (tests must not depend on external resources; use synthetic data or bundled datasets)
- **Using test classes instead of standalone functions** (cuML prefers `test_foo_bar()` functions over `class TestFoo`)
- **New estimator not added to sklearn compatibility tests** (add to `test_sklearn_compatibility.py` estimator list)
### Security
- Unsafe deserialization of model files (using `pickle.load` or `pickle.loads`)
- Insufficient error handling exposing internal details
- Missing bounds checking allowing resource exhaustion
### Documentation
- Missing or incorrect docstrings for public methods
- Hyperparameters not documented
- Missing scikit-learn compatibility notes
- **New estimator not added to `docs/source/api.rst`**
- **New cuml.accel-supported estimator not added to `docs/source/cuml-accel/faq.rst`**
## MEDIUM Issues (Comment Selectively)
- Edge cases not handled (empty datasets, single sample)
- Missing input validation for edge cases
- Deprecated API usage
- **Potential input type confusion** (unclear if accepting cuDF, NumPy, or both)
- Minor inefficiencies in non-critical code paths
## Review Protocol
1. **Scikit-learn compatibility**: Do method signatures match? Required attributes present? Behavior consistent?
2. **Algorithm correctness**: Does the ML logic produce correct results? Matches scikit-learn output?
3. **Input handling**: Proper handling of cuDF/pandas/NumPy inputs? Type coercion correct?
4. **Model state management**: Parameters initialized correctly? State consistent across fit/predict/transform?
5. **API stability**: Breaking changes to Python APIs?
6. **Input validation**: Dataset dimension checks? Parameter validation?
7. **Ask, don't tell**: "Have you considered X?" not "You should do X"
## Quality Threshold
Before commenting, ask:
1. Is this actually wrong/risky, or just different?
2. Would this cause a real problem (wrong results, crash, API break)?
3. Does this comment add unique value?
**If no to any: Skip the comment.**
## Output Format
- Use severity labels: CRITICAL, HIGH, MEDIUM
- Be concise: One-line issue summary + one-line impact
- Provide code suggestions when you have concrete fixes
- No preamble or sign-off
## Examples to Follow
**CRITICAL** (incorrect array order passed to C++ layer):
```
CRITICAL: Array passed with wrong memory order to C++ function
Issue: fit() passes C-order array to C++ expecting F-order
Why: Incorrectly overriding default F-order with C-order
Impact: Incorrect results, potential segfaults
Example bug in estimator:
def fit(self, X, y=None):
X_m, *_ = input_to_cuml_array(X, order='C') # Wrong: C++ expects F-order
cdef uintptr_t X_ptr = X_m.ptr
# C++ receives row-major but expects column-major
self._cpp_fit(X_ptr, X_m.shape[0], X_m.shape[1])
Suggested fix:
def fit(self, X, y=None):
X_m, *_ = input_to_cuml_array(X) # Correct: uses F-order default
cdef uintptr_t X_ptr = X_m.ptr
self._cpp_fit(X_ptr, X_m.shape[0], X_m.shape[1])
```
**HIGH** (missing input validation):
```
HIGH: Missing input dimension validation
Issue: No check for n_features matching between fit and predict
Why: Can cause silent wrong results or cryptic CUDA errors
Suggested fix:
def predict(self, X):
check_is_fitted(self)
X = self._validate_data(X, reset=False)
# ... rest of predict
```
**HIGH** (input type handling):
```
HIGH: Incorrect input type handling
Issue: Function assumes NumPy array but receives cuDF DataFrame
Why: Silent data corruption from incorrect memory access
Suggested fix:
X = input_to_cuml_array(X, order='C').array
```
**CRITICAL** (sklearn API mismatch):
```
CRITICAL: Parameter default differs from scikit-learn
Issue: n_clusters defaults to 5, scikit-learn defaults to 8
Why: Breaks user expectations and compatibility
Consider: Match scikit-learn default or document the difference prominently
```
**CRITICAL** (fitted attribute in constructor):
```
CRITICAL: Fitted attribute initialized in __init__
Issue: self.shrinkage_ = None in __init__
Why: Violates sklearn convention - fitted attributes (trailing _) should only exist after fit()
Impact: Fails sklearn check_estimator and confuses users about fitted state
Suggested fix:
# Remove from __init__, only set in fit()
def __init__(self, ...):
self.store_precision = store_precision # OK: parameter
# Don't do: self.shrinkage_ = None # BAD: fitted attribute
```
## Examples to Avoid
**Boilerplate** (avoid):
- "Machine Learning: K-means is a standard clustering algorithm..."
- "API Design: Consistent naming improves usability..."
**Subjective style** (ignore):
- "Consider using a list comprehension here"
- "This function could be split into smaller functions"
- "Prefer f-strings over .format()"
---
## Python-Specific Considerations
**Scikit-learn Compatibility**:
- API signatures and behavior should match scikit-learn
- Required attributes after fit: `n_features_in_`, `feature_names_in_` (if applicable), algorithm-specific (`coef_`, `cluster_centers_`, etc.)
- Parameter names and defaults should match scikit-learn conventions
- Use `check_is_fitted()` before predict/transform
- Only parameters should be set in `__init__`, never fitted attributes (no `self.coef_ = None`)
- New estimators must be added to sklearn compatibility test list in `test_sklearn_compatibility.py`
**Input Handling**:
- Support cuDF, pandas, and NumPy inputs appropriately
- Use `input_to_cuml_array()` for consistent input conversion
- Use `input_to_cupy_array()` when you need a cupy array directly (more efficient than converting twice)
- Preserve input type in output where sensible (cuDF in β cuDF out)
- Handle both row-major (C) and column-major (F) order
**Model State Management**:
- fit/predict/transform must maintain consistent state
- fit() should reset all learned attributes
- Don't carry over state from previous fit() calls
**Error Messages**:
- Error messages must be clear and actionable for users
- Include expected vs actual values where helpful
- Reference scikit-learn documentation for API questions
**Testing**:
- Compare numerical results with scikit-learn where applicable
- Test edge cases: empty arrays, single sample, single feature
- Test different input types: cuDF, pandas, NumPy
- Test fit/predict/transform consistency
- Use standalone `test_foo_bar()` functions, not test classes
- Add new estimators to `test_sklearn_compatibility.py` for automatic conformance checking
- Use synthetic data or bundled datasets, never external resources
---
## Common Bug Patterns
### 1. Input Type Handling Confusion
**Pattern**: Incorrect assumptions about input data types (cuDF vs pandas vs NumPy)
**Red flags**:
- Functions assuming specific input type without checking
- Missing conversion logic for different input types
- Direct attribute access that only works for one type
- Not preserving input type in output
**Example bug**: Function assumes `.values` attribute exists (pandas), but receives cuDF DataFrame
### 2. Model State Management
**Pattern**: Model parameters not properly initialized/reset between fit calls
**Red flags**:
- fit() method not clearing previous model state
- Reusing internal buffers without resetting
- Missing initialization of model parameters before training
- Carrying over state from previous fit() affecting new training
**Example bug**: Previous cluster centers leaking into new fit() call
### 3. Scikit-learn API Incompatibility
**Pattern**: Breaking scikit-learn API conventions or missing required methods/attributes
**Red flags**:
- Missing fit/predict/transform methods for estimators
- Function or parameter names differing from scikit-learn without justification
- Different default parameter values from scikit-learn
- Different behavior for edge cases (empty arrays, single sample)
**Example bug**: Scikit-learn estimator has a max_iter parameter and cuML has a max_iters parameters which conceptually refer to the same thing.
### 4. Missing Input Validation
**Pattern**: Not validating inputs before processing
**Red flags**:
- No check for fitted state before predict/transform
- No dimension validation between fit and predict
- No handling of edge cases (empty input, single sample)
**Example bug**: predict() called before fit(), causing cryptic CUDA error instead of clear message
### 5. Constructor State Violations
**Pattern**: Initializing fitted attributes in `__init__` instead of only in `fit()`
**Red flags**:
- `self.coef_ = None` or similar in `__init__`
- Any trailing underscore attribute set in constructor
- Fitted attributes initialized before `fit()` is called
**Example bug**: `self.shrinkage_ = None` in `__init__` violates sklearn convention that fitted attributes only exist after `fit()`
### 6. Test Structure Issues
**Pattern**: Using test classes instead of standalone test functions
**Red flags**:
- `class TestFoo:` grouping tests
- Test methods instead of `test_foo_bar()` functions
- Excessive fixture sharing through class attributes
**Example**: cuML prefers `def test_fit_returns_self():` over `class TestLedoitWolf: def test_fit_returns_self(self):`
---
## Code Review Checklists
### When Reviewing Estimator __init__
- [ ] Are any of the constructor arguments validated or changed in violation of the standard estimator guidelines?
- [ ] Do parameter names and defaults match scikit-learn?
- [ ] Is model state properly initialized (not learned attributes)?
- [ ] Are default values appropriate for all dataset types?
### When Reviewing fit() Methods
- [ ] Is previous model state properly cleaned up?
- [ ] Are required attributes set after fit (`n_features_in_`, etc.)?
- [ ] Is input validated with `_validate_data()` or equivalent?
- [ ] Are hyperparameters validated?
- [ ] Is the reflect decorator applied appropriately?
### When Reviewing predict/transform Methods
- [ ] Is `check_is_fitted()` called?
- [ ] Are input dimensions validated against fitted dimensions?
- [ ] Is input type handled correctly (cuDF, pandas, NumPy)?
- [ ] Is output type consistent with input type?
- [ ] Is the reflect decorator applied appropriately?
### When Reviewing Input Handling
- [ ] Are all input types handled (cuDF, pandas, NumPy)?
- [ ] Is `input_to_cuml_array()` used for conversion?
- [ ] Is memory order (C vs F) handled correctly?
- [ ] Is input type preserved in output where appropriate?
### When Reviewing Scikit-learn Compatibility
- [ ] Do method signatures match scikit-learn?
- [ ] Are required attributes present after fit?
- [ ] Do parameter names match scikit-learn conventions?
- [ ] Is behavior consistent with scikit-learn for edge cases?
- [ ] Are deprecation warnings added for API changes?
### When Reviewing Tests
- [ ] Are numerical results compared with scikit-learn?
- [ ] Are edge cases tested (empty, single sample, high-dimensional)?
- [ ] Are different input types tested (cuDF, pandas, NumPy)?
- [ ] Is fit/predict/transform consistency tested?
- [ ] Are all datasets synthetic or bundled (no external resource dependencies)?
- [ ] Are tests written as standalone functions (not grouped in classes)?
- [ ] Is the new estimator added to `test_sklearn_compatibility.py`?
### When Reviewing New Estimators
- [ ] Is the estimator added to `docs/source/api.rst`?
- [ ] If cuml.accel-compatible, is it added to `docs/source/cuml-accel/faq.rst`?
- [ ] Is it added to `test_sklearn_compatibility.py` for conformance checks?
- [ ] Does `__init__` only set parameters (no fitted attributes like `self.coef_ = None`)?
- [ ] Are `_cpu_class_path`, `_get_param_names`, `_params_from_cpu`, `_params_to_cpu`, `_attrs_from_cpu`, `_attrs_to_cpu` implemented for InteropMixin?
---
**Remember**: Focus on correctness and API compatibility. Catch real bugs (wrong results, API breaks, state corruption), ignore style preferences. For cuML Python: scikit-learn compatibility and correct model state management are paramount.
# UMAP Testing and Embedding Quality Assessment Tools
This directory provides comprehensive tools for both UMAP implementation validation and embedding quality assessment. It serves data scientists, researchers, and developers who need to evaluate the quality of UMAP embeddings or compare different UMAP implementations.
## Overview
The tools in this directory serve three main purposes:
1. **Implementation Testing** (`test_umap.py`): Rigorous validation of cuML UMAP against reference implementations
2. **Embedding Quality Assessment** (`umap_metrics.py`): Comprehensive evaluation tools for measuring the quality of any UMAP embedding
3. **Comparison Implementation** (`run_umap_debug.py`): Detailed analysis comparing cuML UMAP with the reference implementation and allowing debugging
### For Data Scientists
These tools provide **standardized metrics** to evaluate how well your UMAP embeddings preserve data structure. Use them to **quantify embedding quality**, **optimize parameters**, and **generate publication-ready reports** with comprehensive visualizations.
### For Researchers and Developers
These tools enable **rigorous implementation comparison** and provide detailed algorithmic insights including **accuracy benchmarking**, **pipeline debugging**, and **topological analysis** using persistent homology.
## Necessary Dependencies
The following dependencies are **NOT** present in the conda environment and need to be installed separately:
#### Required for Nearest Neighbors search
```bash
conda install -c rapidsai cuvs
```
#### Required for Geodesic Distance Computation
```bash
conda install -c rapidsai cugraph
```
#### Required for Topology Preservation Metrics
```bash
pip install ripser
```
#### Required for Web Report Generation
```bash
pip install plotly
```
## Data Requirements
### Real Dataset Testing
Most of the tests require datasets to be present on disk.
Please first download them,
```bash
conda install -c rapidsai cuvs-bench
python -m cuvs_bench.get_dataset --dataset deep-image-96-angular --normalize
python -m cuvs_bench.get_dataset --dataset fashion-mnist-784-euclidean --normalize
python -m cuvs_bench.get_dataset --dataset gist-960-euclidean --normalize
python -m cuvs_bench.get_dataset --dataset glove-25-angular --normalize
python -m cuvs_bench.get_dataset --dataset mnist-784-euclidean --normalize
python -m cuvs_bench.get_dataset --dataset sift-128-euclidean --normalize
```
Then, allow the datasets to be found by setting the `DATASET_DIR` environment variable:
```bash
export DATASET_DIR=/path/to/benchmark/datasets
```
Expected dataset format:
- Binary files with `.fbin` extension for base vectors
- Datasets should follow the standard ANN benchmark format
## Files Description
### Core Testing Files
- **`test_umap.py`**: Main test suite for UMAP functionality with real-world datasets
- **`umap_metrics.py`**: Comprehensive metrics computation library for UMAP quality assessment
- **`run_umap_debug.py`**: Interactive debugging tool for comparing reference vs cuML implementations
- **`toy_datasets.py`**: Synthetic and real dataset generators for testing
- **`web_results_generation.py`**: Web-based interactive report generation
### Standard Testing (`test_umap.py`)
This file contains tests for real-world datasets commonly used in nearest neighbor search benchmarks:
- **Deep Image 96 Angular**: High-dimensional image features with cosine similarity
- **Fashion-MNIST 784 Euclidean**: Fashion item image embeddings
- **GIST 960 Euclidean**: Image descriptor vectors
- **MNIST 784 Euclidean**: Handwritten digit embeddings
- **SIFT 128 Euclidean**: Scale-invariant feature transform descriptors
#### Key Test Features
- **KNN Accuracy Validation**: Compares k-nearest neighbor search results between cuML and reference implementations, measuring neighbor recall and distance accuracy across different metrics (euclidean, cosine, etc.)
- **Fuzzy Simplicial Set Verification**: Validates the construction of fuzzy simplicial sets by comparing edge weights, graph topology, and membership probabilities between implementations
- **Spectral Initialization Testing**: Compares spectral embedding initialization methods, ensuring consistent starting points for the optimization process
- **Embedding Quality Assessment**: Measures final embedding quality using trustworthiness, continuity, and other established manifold learning metrics
- **Parameter Robustness Testing**: Validates performance across different UMAP parameters (n_neighbors, min_dist, n_components) and dataset characteristics
- **Implementation Consistency**: Ensures cuML produces statistically equivalent results to the reference implementation within acceptable tolerances
- **Performance Regression Detection**: Catches performance degradations or quality regressions in cuML updates
#### Running Tests
```bash
DATASET_DIR=datasets pytest python/cuml/umap_dev_tools/test_umap.py -v
```
### Embedding Quality Assessment (`run_umap_debug.py`)
Interactive tool for UMAP embedding quality assessment and implementation comparison. Provides **comprehensive quality metrics**, **standardized evaluation benchmarks**, and **publication-ready reports**. Also enables **pipeline debugging** and **detailed implementation analysis** across multiple test datasets.
#### Available Datasets
**Synthetic**: Swiss Roll, S-Curve, Sphere, Torus, Gaussian Blobs
**Real**: Iris, Wine, Breast Cancer, Digits, Diabetes
#### Usage Examples
```bash
# Quality assessment with web report
python python/cuml/umap_dev_tools/run_umap_debug.py --implementation cuml --dataset "Swiss Roll" --web-report
# Compare cuML vs reference implementation
python python/cuml/umap_dev_tools/run_umap_debug.py --implementation both --dataset "Swiss Roll" --web-report
# Quick quality check (no web report)
python python/cuml/umap_dev_tools/run_umap_debug.py --dataset "Swiss Roll" --implementation cuml
# List available datasets
python python/cuml/umap_dev_tools/run_umap_debug.py --list-datasets
```
### Quality Metrics Library (`umap_metrics.py`)
This module provides a comprehensive suite of scientifically-validated metrics for assessing UMAP embedding quality. These metrics are based on established literature in manifold learning and dimensionality reduction.
#### Local Structure Preservation
These metrics evaluate how well your embedding preserves local neighborhoods and nearest-neighbor relationships:
- **Trustworthiness**: Quantifies how many of the k-nearest neighbors in the embedding were also k-nearest neighbors in the original space (higher is better, range: 0-1)
- **Continuity**: Measures how many of the k-nearest neighbors in the original space remain k-nearest neighbors in the embedding (higher is better, range: 0-1)
#### Global Structure Preservation
These metrics assess how well large-scale data relationships are maintained:
- **Geodesic Spearman Correlation**: Rank correlation between geodesic distances in original space and Euclidean distances in embedding space
- **Geodesic Pearson Correlation (DEMaP)**: Linear correlation between geodesic and embedded distances - the Distance-based Embedding quality Metric
- **Global Structure Score**: Combined measure of how well overall data topology is preserved
#### Fuzzy Simplicial Set Analysis
For researchers and developers, these metrics analyze the intermediate graph representations:
- **KL Divergence**: Information-theoretic comparison between high-dimensional and low-dimensional fuzzy graphs
- **Jaccard Index**: Proportion of edges that overlap between fuzzy simplicial sets
- **Row-sum L1 Error**: Per-node membership mass differences between graph representations
#### Topology Preservation
Advanced topological analysis using computational topology:
- **Persistent Homology**: Analysis of topological features (holes, connected components) across scales
- **Betti Numbers**: Count of topological features - H0 (connected components) and H1 (loops/cycles)
- **Topological Similarity**: Comparison of persistent diagrams between original and embedded data
#### Interpreting the Metrics
**For Data Scientists:**
- **Trustworthiness & Continuity > 0.9**: Excellent local structure preservation
- **Trustworthiness & Continuity > 0.8**: Good preservation, suitable for most analyses
- **Trustworthiness & Continuity < 0.7**: Poor preservation, consider parameter tuning
- **DEMaP > 0.7**: Good global structure preservation
- **Similar Betti numbers**: Good topological preservation
### Web Report Generation (`web_results_generation.py`)
Creates interactive HTML reports with:
- **Embedding Visualizations**: 2D scatter plots with original data coloring
- **Spectral Initialization Plots**: Visualization of initial embedding states
- **Quality Metrics Tables**: Comprehensive metric comparisons
- **Implementation Comparisons**: Side-by-side reference vs cuML analysis