0
# Testing and Debugging Framework
1
2
Comprehensive testing framework with NumPy comparison utilities, array assertions, and performance benchmarking tools for development and validation. CuPy provides extensive testing infrastructure for ensuring correctness and performance of GPU-accelerated code.
3
4
## Capabilities
5
6
### Array Assertions
7
8
Assertion functions for validating array properties and correctness in tests.
9
10
```python { .api }
11
def assert_allclose(actual, desired, rtol=1e-7, atol=0, err_msg='', verbose=True):
12
"""Assert arrays are element-wise equal within tolerance.
13
14
Args:
15
actual: Actual array
16
desired: Desired array
17
rtol: Relative tolerance
18
atol: Absolute tolerance
19
err_msg: Error message for assertion failure
20
verbose: Print conflicting values on failure
21
22
Raises:
23
AssertionError: If arrays are not close within tolerance
24
"""
25
26
def assert_array_equal(x, y, err_msg='', verbose=True, strides_check=False):
27
"""Assert arrays are exactly equal.
28
29
Args:
30
x: First input array
31
y: Second input array
32
err_msg: Error message for assertion failure
33
verbose: Print conflicting values on failure
34
strides_check: Check array strides are equal
35
36
Raises:
37
AssertionError: If arrays are not equal
38
"""
39
40
def assert_array_almost_equal(x, y, decimal=6, err_msg='', verbose=True):
41
"""Assert arrays are equal up to specified precision.
42
43
Args:
44
x: First input array
45
y: Second input array
46
decimal: Number of decimal places for comparison
47
err_msg: Error message for assertion failure
48
verbose: Print conflicting values on failure
49
50
Raises:
51
AssertionError: If arrays are not almost equal
52
"""
53
54
def assert_array_less(x, y, err_msg='', verbose=True):
55
"""Assert array elements are less than corresponding elements.
56
57
Args:
58
x: First input array
59
y: Second input array
60
err_msg: Error message for assertion failure
61
verbose: Print conflicting values on failure
62
63
Raises:
64
AssertionError: If condition is not satisfied
65
"""
66
67
def assert_array_list_equal(xlist, ylist, err_msg='', verbose=True):
68
"""Assert lists of arrays are equal.
69
70
Args:
71
xlist: First list of arrays
72
ylist: Second list of arrays
73
err_msg: Error message for assertion failure
74
verbose: Print conflicting values on failure
75
76
Raises:
77
AssertionError: If array lists are not equal
78
"""
79
```
80
81
### NumPy Comparison Decorators
82
83
Decorators for testing CuPy functions against NumPy equivalents with automatic array module switching.
84
85
```python { .api }
86
def numpy_cupy_allclose(rtol=1e-7, atol=0, err_msg='', verbose=True,
87
name='xp', type_check=True, accept_error=False,
88
sp_name=None, scipy_name=None, contiguous_check=True,
89
strides_check=False):
90
"""Decorator for testing CuPy vs NumPy with allclose comparison.
91
92
Args:
93
rtol: Relative tolerance
94
atol: Absolute tolerance
95
err_msg: Error message
96
verbose: Print details on failure
97
name: Parameter name for array module (xp)
98
type_check: Check result types match
99
accept_error: Accept errors as long as both raise same error
100
sp_name: Parameter name for sparse module
101
scipy_name: Parameter name for scipy module
102
contiguous_check: Check array contiguity
103
strides_check: Check array strides
104
105
Example:
106
@numpy_cupy_allclose()
107
def test_function(xp):
108
a = xp.array([1, 2, 3])
109
return xp.sum(a)
110
"""
111
112
def numpy_cupy_array_equal(err_msg='', verbose=True, name='xp',
113
type_check=True, accept_error=False,
114
sp_name=None, scipy_name=None, strides_check=False):
115
"""Decorator for testing CuPy vs NumPy with exact equality.
116
117
Args:
118
err_msg: Error message
119
verbose: Print details on failure
120
name: Parameter name for array module
121
type_check: Check result types match
122
accept_error: Accept matching errors
123
sp_name: Parameter name for sparse module
124
scipy_name: Parameter name for scipy module
125
strides_check: Check array strides
126
127
Example:
128
@numpy_cupy_array_equal()
129
def test_integer_function(xp):
130
a = xp.array([1, 2, 3])
131
return xp.argmax(a)
132
"""
133
134
def numpy_cupy_raises(accept_error=Exception, name='xp', sp_name=None, scipy_name=None):
135
"""Decorator for testing that both NumPy and CuPy raise exceptions.
136
137
Args:
138
accept_error: Expected exception type(s)
139
name: Parameter name for array module
140
sp_name: Parameter name for sparse module
141
scipy_name: Parameter name for scipy module
142
143
Example:
144
@numpy_cupy_raises()
145
def test_invalid_operation(xp):
146
a = xp.array([1, 2, 3])
147
return a / xp.array([0, 1, 2]) # Division by zero
148
"""
149
```
150
151
### Test Data Generation
152
153
Functions for generating test arrays with specific properties and patterns.
154
155
```python { .api }
156
def shaped_arange(shape, xp=None, dtype=float, order='C'):
157
"""Generate array with arange values in specified shape.
158
159
Args:
160
shape: Output array shape
161
xp: Array module (numpy or cupy)
162
dtype: Data type
163
order: Memory layout
164
165
Returns:
166
Array: Shaped arange array
167
168
Example:
169
arr = shaped_arange((2, 3), xp=cp, dtype=int)
170
# Returns: [[0, 1, 2], [3, 4, 5]]
171
"""
172
173
def shaped_random(shape, xp=None, dtype=float, seed=0, scale=10):
174
"""Generate random array with specified properties.
175
176
Args:
177
shape: Output array shape
178
xp: Array module (numpy or cupy)
179
dtype: Data type
180
seed: Random seed for reproducibility
181
scale: Scale factor for random values
182
183
Returns:
184
Array: Random array with specified shape and properties
185
"""
186
187
def shaped_reverse_arange(shape, xp=None, dtype=float):
188
"""Generate array with reverse arange values.
189
190
Args:
191
shape: Output array shape
192
xp: Array module
193
dtype: Data type
194
195
Returns:
196
Array: Reverse arange array
197
"""
198
199
def for_dtypes(dtypes, name='dtype'):
200
"""Parameterize test for multiple data types.
201
202
Args:
203
dtypes: List of data types to test
204
name: Parameter name for dtype
205
206
Returns:
207
Decorator: Parameterized test decorator
208
"""
209
210
def for_all_dtypes(name='dtype', no_float16=False, no_bool=False,
211
no_complex=False, additional_args=()):
212
"""Parameterize test for all standard data types.
213
214
Args:
215
name: Parameter name for dtype
216
no_float16: Exclude float16 type
217
no_bool: Exclude boolean type
218
no_complex: Exclude complex types
219
additional_args: Additional test parameters
220
221
Returns:
222
Decorator: Parameterized test decorator
223
"""
224
225
def for_float_dtypes(name='dtype', no_float16=False, additional_args=()):
226
"""Parameterize test for floating-point data types.
227
228
Args:
229
name: Parameter name for dtype
230
no_float16: Exclude float16 type
231
additional_args: Additional test parameters
232
233
Returns:
234
Decorator: Parameterized test decorator
235
"""
236
237
def for_int_dtypes(name='dtype', no_bool=False, additional_args=()):
238
"""Parameterize test for integer data types.
239
240
Args:
241
name: Parameter name for dtype
242
no_bool: Exclude boolean type
243
additional_args: Additional test parameters
244
245
Returns:
246
Decorator: Parameterized test decorator
247
"""
248
249
def for_signed_dtypes(name='dtype', additional_args=()):
250
"""Parameterize test for signed data types."""
251
252
def for_unsigned_dtypes(name='dtype', additional_args=()):
253
"""Parameterize test for unsigned data types."""
254
255
def for_complex_dtypes(name='dtype', additional_args=()):
256
"""Parameterize test for complex data types."""
257
```
258
259
### Test Parameterization
260
261
Advanced parameterization utilities for comprehensive test coverage.
262
263
```python { .api }
264
def parameterize(*params, **kwargs):
265
"""Parameterize test with multiple parameter combinations.
266
267
Args:
268
*params: Parameter specifications
269
**kwargs: Named parameter specifications
270
271
Returns:
272
Decorator: Parameterized test decorator
273
274
Example:
275
@parameterize([
276
{'shape': (10,), 'axis': None},
277
{'shape': (3, 4), 'axis': 0},
278
{'shape': (2, 3, 4), 'axis': (1, 2)},
279
])
280
def test_reduction(self, shape, axis):
281
...
282
"""
283
284
def product(parameter_dict):
285
"""Generate Cartesian product of parameters.
286
287
Args:
288
parameter_dict: Dictionary of parameter lists
289
290
Returns:
291
Decorator: Cartesian product test decorator
292
293
Example:
294
@product({
295
'shape': [(10,), (3, 4), (2, 3, 4)],
296
'dtype': [float32, float64],
297
'axis': [None, 0, -1]
298
})
299
def test_function(self, shape, dtype, axis):
300
...
301
"""
302
303
def product_dict(*dicts):
304
"""Generate Cartesian product from multiple dictionaries.
305
306
Args:
307
*dicts: Parameter dictionaries to combine
308
309
Returns:
310
Decorator: Combined parameter test decorator
311
"""
312
```
313
314
### Performance Testing
315
316
Utilities for benchmarking and performance validation.
317
318
```python { .api }
319
def time_cupy_vs_numpy(func, args=(), number=1, repeat=3, setup='pass'):
320
"""Compare execution time between CuPy and NumPy.
321
322
Args:
323
func: Function to benchmark
324
args: Function arguments
325
number: Number of executions per timing
326
repeat: Number of timing repetitions
327
setup: Setup code
328
329
Returns:
330
dict: Timing results for both libraries
331
"""
332
333
class Timer:
334
"""High-resolution timer for performance measurements.
335
336
Example:
337
with Timer() as timer:
338
result = expensive_operation()
339
print(f"Operation took {timer.elapsed:.3f} seconds")
340
"""
341
342
def __enter__(self):
343
self.start_time = time.perf_counter()
344
return self
345
346
def __exit__(self, exc_type, exc_val, exc_tb):
347
self.end_time = time.perf_counter()
348
self.elapsed = self.end_time - self.start_time
349
350
def benchmark_function(func, args=(), warmup=3, number=10):
351
"""Benchmark function execution time.
352
353
Args:
354
func: Function to benchmark
355
args: Function arguments
356
warmup: Number of warmup runs
357
number: Number of timing runs
358
359
Returns:
360
dict: Benchmark statistics (mean, std, min, max)
361
"""
362
```
363
364
### Test Markers and Fixtures
365
366
Test execution control and resource management utilities.
367
368
```python { .api }
369
def gpu(*args, **kwargs):
370
"""Mark test as requiring GPU.
371
372
Args:
373
*args: GPU requirements
374
**kwargs: Additional GPU parameters
375
376
Returns:
377
Decorator: GPU test marker
378
"""
379
380
def multi_gpu(gpu_num):
381
"""Mark test as requiring multiple GPUs.
382
383
Args:
384
gpu_num: Number of GPUs required
385
386
Returns:
387
Decorator: Multi-GPU test marker
388
"""
389
390
def slow():
391
"""Mark test as slow-running.
392
393
Returns:
394
Decorator: Slow test marker
395
"""
396
397
def condition(cond):
398
"""Conditional test execution.
399
400
Args:
401
cond: Boolean condition for test execution
402
403
Returns:
404
Decorator: Conditional test decorator
405
"""
406
407
def repeat(n):
408
"""Repeat test execution n times.
409
410
Args:
411
n: Number of repetitions
412
413
Returns:
414
Decorator: Test repetition decorator
415
"""
416
```
417
418
### Error Handling and Validation
419
420
Utilities for testing error conditions and input validation.
421
422
```python { .api }
423
def raises(exception_type, match=None):
424
"""Context manager for testing exceptions.
425
426
Args:
427
exception_type: Expected exception type
428
match: Regex pattern for exception message
429
430
Returns:
431
Context manager for exception testing
432
433
Example:
434
with raises(ValueError, match="invalid shape"):
435
cp.array([1, 2, 3]).reshape((2, 2))
436
"""
437
438
def warns(warning_type, match=None):
439
"""Context manager for testing warnings.
440
441
Args:
442
warning_type: Expected warning type
443
match: Regex pattern for warning message
444
445
Returns:
446
Context manager for warning testing
447
"""
448
449
def assert_warns(warning_class, func, *args, **kwargs):
450
"""Assert function raises specific warning.
451
452
Args:
453
warning_class: Expected warning type
454
func: Function to test
455
*args: Function arguments
456
**kwargs: Function keyword arguments
457
458
Returns:
459
Function result if warning is raised
460
"""
461
462
def suppress_warnings():
463
"""Context manager for suppressing warnings during tests.
464
465
Returns:
466
Context manager for warning suppression
467
"""
468
```
469
470
### Custom Test Utilities
471
472
Specialized utilities for CuPy-specific testing scenarios.
473
474
```python { .api }
475
def assert_array_equal_ex(x, y, strides_check=False):
476
"""Extended array equality with stride checking.
477
478
Args:
479
x: First array
480
y: Second array
481
strides_check: Check stride equality
482
483
Raises:
484
AssertionError: If arrays differ
485
"""
486
487
def generate_matrix(shape, dtype, matrix_type='random', seed=None):
488
"""Generate test matrix with specific properties.
489
490
Args:
491
shape: Matrix shape
492
dtype: Data type
493
matrix_type: Matrix type ('random', 'identity', 'zeros', 'ones', 'diagonal')
494
seed: Random seed
495
496
Returns:
497
cupy.ndarray: Generated test matrix
498
"""
499
500
def check_elementwise_kernel(kernel_func, numpy_func, test_cases, rtol=1e-7, atol=0):
501
"""Validate custom elementwise kernel against NumPy function.
502
503
Args:
504
kernel_func: CuPy kernel function
505
numpy_func: Reference NumPy function
506
test_cases: List of test input arrays
507
rtol: Relative tolerance
508
atol: Absolute tolerance
509
510
Raises:
511
AssertionError: If kernel results differ from NumPy
512
"""
513
514
def check_reduction_kernel(kernel_func, numpy_func, test_cases, axes_list=None):
515
"""Validate custom reduction kernel against NumPy function.
516
517
Args:
518
kernel_func: CuPy reduction kernel
519
numpy_func: Reference NumPy function
520
test_cases: List of test input arrays
521
axes_list: List of axes to test
522
523
Raises:
524
AssertionError: If kernel results differ from NumPy
525
"""
526
```
527
528
## Usage Examples
529
530
### Basic Array Testing
531
532
```python
533
import cupy as cp
534
import numpy as np
535
from cupy.testing import assert_allclose, assert_array_equal
536
537
# Test basic array operations
538
def test_array_creation():
539
"""Test array creation functions."""
540
# Test zeros
541
cupy_zeros = cp.zeros((3, 4), dtype=cp.float32)
542
numpy_zeros = np.zeros((3, 4), dtype=np.float32)
543
544
assert_array_equal(cupy_zeros, numpy_zeros)
545
print("✓ Array creation test passed")
546
547
def test_mathematical_operations():
548
"""Test mathematical operations."""
549
# Create test data
550
np.random.seed(42)
551
data_np = np.random.randn(100, 50).astype(np.float32)
552
data_cp = cp.asarray(data_np)
553
554
# Test sum operation
555
sum_np = np.sum(data_np, axis=1)
556
sum_cp = cp.sum(data_cp, axis=1)
557
558
assert_allclose(sum_cp, sum_np, rtol=1e-6)
559
print("✓ Sum operation test passed")
560
561
# Test complex operation
562
result_np = np.sqrt(np.sum(data_np**2, axis=1))
563
result_cp = cp.sqrt(cp.sum(data_cp**2, axis=1))
564
565
assert_allclose(result_cp, result_np, rtol=1e-6)
566
print("✓ Complex operation test passed")
567
568
# Run basic tests
569
test_array_creation()
570
test_mathematical_operations()
571
```
572
573
### NumPy Comparison Testing
574
575
```python
576
import cupy as cp
577
from cupy.testing import numpy_cupy_allclose, numpy_cupy_array_equal, shaped_arange
578
579
class TestMathFunctions:
580
"""Test mathematical functions using comparison decorators."""
581
582
@numpy_cupy_allclose(rtol=1e-6)
583
def test_trigonometric_functions(self, xp):
584
"""Test trigonometric functions."""
585
x = shaped_arange((100,), xp, dtype=xp.float32)
586
x = x / 10 # Scale to reasonable range
587
588
# Test multiple trig functions
589
sin_result = xp.sin(x)
590
cos_result = xp.cos(x)
591
combined = sin_result**2 + cos_result**2 # Should be close to 1
592
593
return combined
594
595
@numpy_cupy_allclose(rtol=1e-5)
596
def test_reduction_operations(self, xp):
597
"""Test reduction operations."""
598
data = shaped_arange((50, 40), xp, dtype=xp.float64)
599
600
# Test various reductions
601
results = {
602
'sum_all': xp.sum(data),
603
'sum_axis0': xp.sum(data, axis=0),
604
'sum_axis1': xp.sum(data, axis=1),
605
'mean_all': xp.mean(data),
606
'std_all': xp.std(data),
607
}
608
609
return results
610
611
@numpy_cupy_array_equal()
612
def test_indexing_operations(self, xp):
613
"""Test indexing operations (exact equality expected)."""
614
data = shaped_arange((20, 30), xp, dtype=xp.int32)
615
616
# Test various indexing patterns
617
results = {
618
'slice1': data[5:15, 10:25],
619
'stride': data[::2, ::3],
620
'boolean': data[data > 200],
621
'fancy': data[[1, 3, 5], [2, 4, 6]],
622
}
623
624
return results
625
626
# Run comparison tests
627
test_class = TestMathFunctions()
628
629
print("Running NumPy comparison tests...")
630
631
try:
632
result = test_class.test_trigonometric_functions()
633
print("✓ Trigonometric functions test passed")
634
except Exception as e:
635
print(f"✗ Trigonometric functions test failed: {e}")
636
637
try:
638
result = test_class.test_reduction_operations()
639
print("✓ Reduction operations test passed")
640
except Exception as e:
641
print(f"✗ Reduction operations test failed: {e}")
642
643
try:
644
result = test_class.test_indexing_operations()
645
print("✓ Indexing operations test passed")
646
except Exception as e:
647
print(f"✗ Indexing operations test failed: {e}")
648
```
649
650
### Parameterized Testing
651
652
```python
653
import cupy as cp
654
from cupy.testing import for_all_dtypes, for_float_dtypes, parameterize, shaped_arange
655
656
class TestParameterizedFunctions:
657
"""Test functions with multiple parameter combinations."""
658
659
@for_all_dtypes(no_complex=True)
660
def test_array_creation_dtypes(self, dtype):
661
"""Test array creation for all data types."""
662
# Create array with specific dtype
663
arr = cp.zeros(100, dtype=dtype)
664
665
# Verify dtype
666
assert arr.dtype == dtype
667
668
# Verify values
669
expected = cp.zeros(100, dtype=dtype)
670
cp.testing.assert_array_equal(arr, expected)
671
672
print(f"✓ Array creation test passed for dtype: {dtype}")
673
674
@for_float_dtypes()
675
def test_mathematical_precision(self, dtype):
676
"""Test mathematical operations for floating-point types."""
677
# Generate test data
678
data = shaped_arange((50,), dtype=dtype) / 10
679
680
# Test operation that requires precision
681
result = cp.exp(cp.log(data + 1)) # Should equal data + 1
682
expected = data + 1
683
684
# Adjust tolerance based on dtype precision
685
if dtype == cp.float16:
686
rtol = 1e-3
687
elif dtype == cp.float32:
688
rtol = 1e-6
689
else: # float64
690
rtol = 1e-12
691
692
cp.testing.assert_allclose(result, expected, rtol=rtol)
693
print(f"✓ Mathematical precision test passed for dtype: {dtype}")
694
695
@parameterize([
696
{'shape': (100,), 'axis': None},
697
{'shape': (10, 10), 'axis': 0},
698
{'shape': (10, 10), 'axis': 1},
699
{'shape': (5, 4, 3), 'axis': (0, 2)},
700
{'shape': (2, 3, 4, 5), 'axis': None},
701
])
702
def test_sum_various_shapes(self, shape, axis):
703
"""Test sum operation for various shapes and axes."""
704
# Create test array
705
arr = shaped_arange(shape, cp, dtype=cp.float32)
706
707
# Compute sum
708
result = cp.sum(arr, axis=axis)
709
710
# Verify result properties
711
if axis is None:
712
assert result.ndim == 0 # Scalar result
713
else:
714
# Check output shape
715
expected_shape = list(shape)
716
if isinstance(axis, int):
717
expected_shape.pop(axis)
718
else:
719
for ax in sorted(axis, reverse=True):
720
expected_shape.pop(ax)
721
expected_shape = tuple(expected_shape) if expected_shape else ()
722
723
assert result.shape == expected_shape
724
725
print(f"✓ Sum test passed for shape: {shape}, axis: {axis}")
726
727
# Run parameterized tests
728
test_class = TestParameterizedFunctions()
729
730
# Test all data types
731
print("Testing array creation for all data types:")
732
for dtype in [cp.int8, cp.int16, cp.int32, cp.int64,
733
cp.float16, cp.float32, cp.float64, cp.bool_]:
734
try:
735
test_class.test_array_creation_dtypes(dtype)
736
except Exception as e:
737
print(f"✗ Failed for dtype {dtype}: {e}")
738
739
# Test floating-point precision
740
print("\nTesting mathematical precision for float types:")
741
for dtype in [cp.float16, cp.float32, cp.float64]:
742
try:
743
test_class.test_mathematical_precision(dtype)
744
except Exception as e:
745
print(f"✗ Failed for dtype {dtype}: {e}")
746
747
# Test various shapes manually (simulating parameterized test)
748
print("\nTesting sum operation for various shapes:")
749
test_params = [
750
{'shape': (100,), 'axis': None},
751
{'shape': (10, 10), 'axis': 0},
752
{'shape': (10, 10), 'axis': 1},
753
{'shape': (5, 4, 3), 'axis': (0, 2)},
754
{'shape': (2, 3, 4, 5), 'axis': None},
755
]
756
757
for params in test_params:
758
try:
759
test_class.test_sum_various_shapes(**params)
760
except Exception as e:
761
print(f"✗ Failed for params {params}: {e}")
762
```
763
764
### Performance Testing and Benchmarking
765
766
```python
767
import cupy as cp
768
import numpy as np
769
import time
770
771
class PerformanceTester:
772
"""Performance testing utilities."""
773
774
def __init__(self):
775
self.warmup_runs = 3
776
self.timing_runs = 10
777
778
def benchmark_function(self, func, args=(), warmup=None, runs=None):
779
"""Benchmark function execution time."""
780
warmup = warmup or self.warmup_runs
781
runs = runs or self.timing_runs
782
783
# Warmup runs
784
for _ in range(warmup):
785
result = func(*args)
786
if hasattr(cp, 'cuda'):
787
cp.cuda.Stream.null.synchronize()
788
789
# Timing runs
790
times = []
791
for _ in range(runs):
792
start_time = time.perf_counter()
793
result = func(*args)
794
if hasattr(cp, 'cuda'):
795
cp.cuda.Stream.null.synchronize()
796
end_time = time.perf_counter()
797
times.append(end_time - start_time)
798
799
return {
800
'mean': np.mean(times),
801
'std': np.std(times),
802
'min': np.min(times),
803
'max': np.max(times),
804
'times': times,
805
'result': result
806
}
807
808
def compare_cupy_numpy(self, operation_name, cupy_func, numpy_func,
809
cupy_args=(), numpy_args=None):
810
"""Compare CuPy and NumPy performance."""
811
numpy_args = numpy_args or cupy_args
812
813
print(f"\nBenchmarking {operation_name}:")
814
815
# Benchmark NumPy
816
numpy_stats = self.benchmark_function(numpy_func, numpy_args)
817
818
# Benchmark CuPy
819
cupy_stats = self.benchmark_function(cupy_func, cupy_args)
820
821
# Calculate speedup
822
speedup = numpy_stats['mean'] / cupy_stats['mean']
823
824
print(f" NumPy: {numpy_stats['mean']*1000:6.2f} ± {numpy_stats['std']*1000:5.2f} ms")
825
print(f" CuPy: {cupy_stats['mean']*1000:6.2f} ± {cupy_stats['std']*1000:5.2f} ms")
826
print(f" Speedup: {speedup:.2f}x")
827
828
return {
829
'numpy': numpy_stats,
830
'cupy': cupy_stats,
831
'speedup': speedup
832
}
833
834
# Performance testing examples
835
tester = PerformanceTester()
836
837
# Large matrix operations
838
n = 5000
839
print(f"Performance testing with matrices of size {n}x{n}")
840
841
# Matrix multiplication
842
A_np = np.random.randn(n, n).astype(np.float32)
843
B_np = np.random.randn(n, n).astype(np.float32)
844
A_cp = cp.asarray(A_np)
845
B_cp = cp.asarray(B_np)
846
847
results = tester.compare_cupy_numpy(
848
"Matrix Multiplication",
849
lambda: cp.dot(A_cp, B_cp),
850
lambda: np.dot(A_np, B_np)
851
)
852
853
# Element-wise operations
854
large_array_np = np.random.randn(10_000_000).astype(np.float32)
855
large_array_cp = cp.asarray(large_array_np)
856
857
results = tester.compare_cupy_numpy(
858
"Element-wise Sin",
859
lambda: cp.sin(large_array_cp),
860
lambda: np.sin(large_array_np)
861
)
862
863
results = tester.compare_cupy_numpy(
864
"Array Sum",
865
lambda: cp.sum(large_array_cp),
866
lambda: np.sum(large_array_np)
867
)
868
869
# FFT operations
870
fft_data_np = np.random.randn(2**20).astype(np.complex64)
871
fft_data_cp = cp.asarray(fft_data_np)
872
873
results = tester.compare_cupy_numpy(
874
"FFT",
875
lambda: cp.fft.fft(fft_data_cp),
876
lambda: np.fft.fft(fft_data_np)
877
)
878
879
# Memory bandwidth test
880
def memory_bandwidth_test():
881
"""Test memory bandwidth with various operations."""
882
sizes = [2**i for i in range(20, 28)] # From 1MB to 1GB
883
884
print("\nMemory Bandwidth Test:")
885
print("Size (MB) Copy (GB/s) Add (GB/s)")
886
887
for size in sizes:
888
n_elements = size
889
n_bytes = n_elements * 4 # float32
890
size_mb = n_bytes / (1024**2)
891
892
if size_mb > 500: # Skip very large sizes
893
break
894
895
# Generate data
896
a = cp.random.randn(n_elements).astype(cp.float32)
897
b = cp.random.randn(n_elements).astype(cp.float32)
898
899
# Test copy bandwidth
900
copy_stats = tester.benchmark_function(lambda: cp.copy(a), runs=5)
901
copy_bandwidth = (n_bytes / copy_stats['mean']) / (1024**3)
902
903
# Test add bandwidth (read a, read b, write result)
904
add_stats = tester.benchmark_function(lambda: a + b, runs=5)
905
add_bandwidth = (3 * n_bytes / add_stats['mean']) / (1024**3)
906
907
print(f"{size_mb:8.1f} {copy_bandwidth:8.1f} {add_bandwidth:8.1f}")
908
909
memory_bandwidth_test()
910
```
911
912
### Custom Kernel Testing
913
914
```python
915
import cupy as cp
916
from cupy import ElementwiseKernel, ReductionKernel
917
918
class CustomKernelTester:
919
"""Test custom CuPy kernels against reference implementations."""
920
921
def test_elementwise_kernel(self):
922
"""Test custom elementwise kernel."""
923
# Define custom kernel
924
square_diff_kernel = ElementwiseKernel(
925
'float32 x, float32 y',
926
'float32 z',
927
'z = (x - y) * (x - y)',
928
'square_diff'
929
)
930
931
# Generate test data
932
n = 10000
933
x = cp.random.randn(n).astype(cp.float32)
934
y = cp.random.randn(n).astype(cp.float32)
935
936
# Test kernel
937
kernel_result = square_diff_kernel(x, y)
938
939
# Reference implementation
940
reference_result = (x - y) ** 2
941
942
# Validate
943
cp.testing.assert_allclose(kernel_result, reference_result, rtol=1e-6)
944
print("✓ Elementwise kernel test passed")
945
946
return kernel_result
947
948
def test_reduction_kernel(self):
949
"""Test custom reduction kernel."""
950
# Define sum of squares kernel
951
sum_of_squares_kernel = ReductionKernel(
952
'float32 x',
953
'float32 y',
954
'x * x', # map expression
955
'a + b', # reduce expression
956
'y = a', # post expression
957
'0', # identity
958
'sum_of_squares'
959
)
960
961
# Generate test data
962
n = 10000
963
x = cp.random.randn(n).astype(cp.float32)
964
965
# Test kernel
966
kernel_result = sum_of_squares_kernel(x)
967
968
# Reference implementation
969
reference_result = cp.sum(x * x)
970
971
# Validate
972
cp.testing.assert_allclose(kernel_result, reference_result, rtol=1e-6)
973
print("✓ Reduction kernel test passed")
974
975
return kernel_result
976
977
def test_complex_kernel_workflow(self):
978
"""Test complex workflow combining multiple kernels."""
979
# Step 1: Normalize data
980
normalize_kernel = ElementwiseKernel(
981
'float32 x, float32 mean, float32 std',
982
'float32 z',
983
'z = (x - mean) / std',
984
'normalize'
985
)
986
987
# Step 2: Compute variance after normalization
988
variance_kernel = ReductionKernel(
989
'float32 x',
990
'float32 var',
991
'x * x',
992
'a + b',
993
'var = a / _in_ind.size()',
994
'0',
995
'variance'
996
)
997
998
# Generate test data
999
n = 50000
1000
data = cp.random.normal(10, 3, n).astype(cp.float32)
1001
1002
# Compute statistics
1003
mean_val = cp.mean(data)
1004
std_val = cp.std(data)
1005
1006
# Apply normalization kernel
1007
normalized = normalize_kernel(data, mean_val, std_val)
1008
1009
# Compute variance of normalized data (should be ~1)
1010
variance = variance_kernel(normalized)
1011
1012
print(f"Original data: mean={float(mean_val):.3f}, std={float(std_val):.3f}")
1013
print(f"Normalized data: mean={float(cp.mean(normalized)):.6f}, variance={float(variance):.6f}")
1014
1015
# Validate normalization
1016
assert abs(float(cp.mean(normalized))) < 1e-6, "Mean should be ~0"
1017
assert abs(float(variance) - 1.0) < 1e-3, "Variance should be ~1"
1018
1019
print("✓ Complex kernel workflow test passed")
1020
1021
return normalized, variance
1022
1023
# Run custom kernel tests
1024
tester = CustomKernelTester()
1025
1026
try:
1027
tester.test_elementwise_kernel()
1028
tester.test_reduction_kernel()
1029
tester.test_complex_kernel_workflow()
1030
print("\n✓ All custom kernel tests passed!")
1031
except Exception as e:
1032
print(f"\n✗ Custom kernel test failed: {e}")
1033
```
1034
1035
### Error Testing and Edge Cases
1036
1037
```python
1038
import cupy as cp
1039
from cupy.testing import assert_array_equal
1040
import pytest
1041
1042
class ErrorTester:
1043
"""Test error conditions and edge cases."""
1044
1045
def test_division_by_zero(self):
1046
"""Test division by zero handling."""
1047
# Test with different settings
1048
a = cp.array([1.0, 2.0, 3.0])
1049
b = cp.array([1.0, 0.0, 2.0])
1050
1051
# Should produce inf without error
1052
result = a / b
1053
expected = cp.array([1.0, cp.inf, 1.5])
1054
1055
cp.testing.assert_array_equal(result, expected)
1056
print("✓ Division by zero test passed")
1057
1058
def test_invalid_shapes(self):
1059
"""Test operations with invalid shapes."""
1060
a = cp.array([[1, 2], [3, 4]])
1061
b = cp.array([1, 2, 3])
1062
1063
# This should raise an error
1064
try:
1065
result = a @ b # Invalid matrix multiplication
1066
assert False, "Expected ValueError for invalid shapes"
1067
except ValueError:
1068
print("✓ Invalid shape error test passed")
1069
1070
def test_out_of_bounds_indexing(self):
1071
"""Test out-of-bounds indexing."""
1072
arr = cp.arange(10)
1073
1074
# This should raise IndexError
1075
try:
1076
value = arr[15]
1077
assert False, "Expected IndexError"
1078
except IndexError:
1079
print("✓ Out-of-bounds indexing test passed")
1080
1081
def test_dtype_overflow(self):
1082
"""Test data type overflow conditions."""
1083
# Test integer overflow
1084
a = cp.array([127], dtype=cp.int8)
1085
b = cp.array([1], dtype=cp.int8)
1086
1087
result = a + b # Should overflow
1088
# Note: behavior may be platform dependent
1089
print(f"Int8 overflow result: {result} (expected: -128 or wrapped value)")
1090
1091
# Test with larger types
1092
large_val = cp.array([2**31 - 1], dtype=cp.int32)
1093
overflow_result = large_val + cp.array([1], dtype=cp.int32)
1094
print(f"Int32 overflow: {large_val} + 1 = {overflow_result}")
1095
1096
print("✓ Dtype overflow test completed")
1097
1098
def test_nan_propagation(self):
1099
"""Test NaN propagation in calculations."""
1100
data = cp.array([1.0, cp.nan, 3.0, 4.0])
1101
1102
# Test various operations
1103
sum_result = cp.sum(data) # Should be NaN
1104
nansum_result = cp.nansum(data) # Should ignore NaN
1105
1106
assert cp.isnan(sum_result), "Sum should propagate NaN"
1107
assert not cp.isnan(nansum_result), "nansum should ignore NaN"
1108
assert cp.isclose(nansum_result, 8.0), f"nansum should equal 8, got {nansum_result}"
1109
1110
print("✓ NaN propagation test passed")
1111
1112
def test_memory_limits(self):
1113
"""Test behavior near memory limits."""
1114
# Try to allocate very large array
1115
try:
1116
# This will likely fail on most systems
1117
huge_array = cp.zeros((100000, 100000), dtype=cp.float64)
1118
print(f"Successfully allocated huge array: {huge_array.shape}")
1119
except cp.cuda.memory.OutOfMemoryError:
1120
print("✓ Out of memory error handled correctly")
1121
except MemoryError:
1122
print("✓ Memory error handled correctly")
1123
1124
# Test gradual memory allocation
1125
arrays = []
1126
try:
1127
for i in range(100):
1128
arr = cp.random.randn(1000, 1000)
1129
arrays.append(arr)
1130
if i % 20 == 0:
1131
print(f"Allocated {i+1} arrays, total memory: ~{(i+1)*8:.1f} MB")
1132
except (cp.cuda.memory.OutOfMemoryError, MemoryError):
1133
print(f"Memory limit reached after {len(arrays)} arrays")
1134
1135
# Clean up
1136
del arrays
1137
cp.get_default_memory_pool().free_all_blocks()
1138
print("✓ Memory cleanup completed")
1139
1140
# Run error tests
1141
error_tester = ErrorTester()
1142
1143
print("Running error and edge case tests:")
1144
1145
try:
1146
error_tester.test_division_by_zero()
1147
error_tester.test_invalid_shapes()
1148
error_tester.test_out_of_bounds_indexing()
1149
error_tester.test_dtype_overflow()
1150
error_tester.test_nan_propagation()
1151
error_tester.test_memory_limits()
1152
1153
print("\n✓ All error handling tests completed successfully!")
1154
1155
except Exception as e:
1156
print(f"\n✗ Error in error testing: {e}")
1157
```