cumo 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +10 -0
  3. data/3rd_party/mkmf-cu/lib/mkmf-cu/cli.rb +1 -1
  4. data/CHANGELOG.md +50 -2
  5. data/README.md +37 -0
  6. data/cumo.gemspec +2 -2
  7. data/ext/cumo/cuda/cublas.c +12 -19
  8. data/ext/cumo/cuda/cudnn.c +10 -18
  9. data/ext/cumo/cuda/cudnn_impl.cpp +23 -30
  10. data/ext/cumo/cuda/cusolver.c +1407 -0
  11. data/ext/cumo/cuda/nvrtc.c +77 -11
  12. data/ext/cumo/cuda/runtime.c +137 -35
  13. data/ext/cumo/cumo.c +2 -0
  14. data/ext/cumo/depend.erb +8 -4
  15. data/ext/cumo/extconf.rb +37 -34
  16. data/ext/cumo/include/cumo/atomic.h +27 -0
  17. data/ext/cumo/include/cumo/cuda/cusolver.h +35 -0
  18. data/ext/cumo/include/cumo/cuda/runtime.h +37 -5
  19. data/ext/cumo/include/cumo/intern.h +2 -2
  20. data/ext/cumo/include/cumo/narray.h +2 -2
  21. data/ext/cumo/include/cumo/types/int_macro_kernel.h +1 -1
  22. data/ext/cumo/include/cumo/types/uint_macro_kernel.h +1 -2
  23. data/ext/cumo/include/cumo.h +2 -2
  24. data/ext/cumo/narray/array.c +7 -6
  25. data/ext/cumo/narray/data.c +54 -19
  26. data/ext/cumo/narray/data_kernel.cu +39 -0
  27. data/ext/cumo/narray/gen/cogen_kernel.rb +58 -1
  28. data/ext/cumo/narray/gen/cogen_runner.rb +16 -0
  29. data/ext/cumo/narray/gen/erbpp2.rb +7 -1
  30. data/ext/cumo/narray/gen/kernel_parts.rb +17 -0
  31. data/ext/cumo/narray/gen/narray_def.rb +2 -2
  32. data/ext/cumo/narray/gen/spec.rb +10 -0
  33. data/ext/cumo/narray/gen/tmpl/accum.c +9 -8
  34. data/ext/cumo/narray/gen/tmpl/accum_arg.c +3 -3
  35. data/ext/cumo/narray/gen/tmpl/accum_arg_kernel.cu +6 -2
  36. data/ext/cumo/narray/gen/tmpl/accum_binary.c +5 -5
  37. data/ext/cumo/narray/gen/tmpl/accum_binary_kernel.cu +2 -0
  38. data/ext/cumo/narray/gen/tmpl/accum_index.c +3 -3
  39. data/ext/cumo/narray/gen/tmpl/accum_index_kernel.cu +6 -2
  40. data/ext/cumo/narray/gen/tmpl/allocate.c +3 -1
  41. data/ext/cumo/narray/gen/tmpl/aref.c +2 -2
  42. data/ext/cumo/narray/gen/tmpl/aref_cpu.c +2 -2
  43. data/ext/cumo/narray/gen/tmpl/aset.c +3 -3
  44. data/ext/cumo/narray/gen/tmpl/at.c +2 -2
  45. data/ext/cumo/narray/gen/tmpl/binary.c +2 -2
  46. data/ext/cumo/narray/gen/tmpl/binary2.c +2 -2
  47. data/ext/cumo/narray/gen/tmpl/binary_s.c +3 -3
  48. data/ext/cumo/narray/gen/tmpl/bincount.c +7 -7
  49. data/ext/cumo/narray/gen/tmpl/cast.c +4 -2
  50. data/ext/cumo/narray/gen/tmpl/clip.c +3 -3
  51. data/ext/cumo/narray/gen/tmpl/coerce_cast.c +1 -1
  52. data/ext/cumo/narray/gen/tmpl/complex_accum_kernel.cu +51 -1
  53. data/ext/cumo/narray/gen/tmpl/cond_binary.c +2 -2
  54. data/ext/cumo/narray/gen/tmpl/cond_unary.c +1 -1
  55. data/ext/cumo/narray/gen/tmpl/cum.c +4 -7
  56. data/ext/cumo/narray/gen/tmpl/each.c +1 -1
  57. data/ext/cumo/narray/gen/tmpl/ewcomp.c +6 -5
  58. data/ext/cumo/narray/gen/tmpl/extract.c +1 -1
  59. data/ext/cumo/narray/gen/tmpl/extract_cpu.c +1 -1
  60. data/ext/cumo/narray/gen/tmpl/eye.c +5 -5
  61. data/ext/cumo/narray/gen/tmpl/fill.c +2 -2
  62. data/ext/cumo/narray/gen/tmpl/float_accum_kernel.cu +37 -1
  63. data/ext/cumo/narray/gen/tmpl/format.c +2 -2
  64. data/ext/cumo/narray/gen/tmpl/format_to_a.c +2 -2
  65. data/ext/cumo/narray/gen/tmpl/frexp.c +2 -2
  66. data/ext/cumo/narray/gen/tmpl/gemm.c +3 -3
  67. data/ext/cumo/narray/gen/tmpl/inspect.c +1 -1
  68. data/ext/cumo/narray/gen/tmpl/layer_norm.c +5 -5
  69. data/ext/cumo/narray/gen/tmpl/logseq.c +4 -4
  70. data/ext/cumo/narray/gen/tmpl/median.c +5 -5
  71. data/ext/cumo/narray/gen/tmpl/minmax.c +4 -4
  72. data/ext/cumo/narray/gen/tmpl/poly.c +2 -2
  73. data/ext/cumo/narray/gen/tmpl/pow.c +60 -15
  74. data/ext/cumo/narray/gen/tmpl/pow_kernel.cu +28 -16
  75. data/ext/cumo/narray/gen/tmpl/rand.c +7 -9
  76. data/ext/cumo/narray/gen/tmpl/rand_norm.c +6 -9
  77. data/ext/cumo/narray/gen/tmpl/real_accum_kernel.cu +3 -1
  78. data/ext/cumo/narray/gen/tmpl/rms_norm.c +4 -4
  79. data/ext/cumo/narray/gen/tmpl/seq.c +3 -3
  80. data/ext/cumo/narray/gen/tmpl/sort.c +4 -4
  81. data/ext/cumo/narray/gen/tmpl/sort_index.c +3 -3
  82. data/ext/cumo/narray/gen/tmpl/store.c +2 -2
  83. data/ext/cumo/narray/gen/tmpl/store_array.c +9 -6
  84. data/ext/cumo/narray/gen/tmpl/store_kernel.cu +1 -1
  85. data/ext/cumo/narray/gen/tmpl/to_a.c +1 -1
  86. data/ext/cumo/narray/gen/tmpl/unary.c +1 -1
  87. data/ext/cumo/narray/gen/tmpl/unary2.c +1 -1
  88. data/ext/cumo/narray/gen/tmpl/unary_ret2.c +1 -1
  89. data/ext/cumo/narray/gen/tmpl/unary_s.c +2 -2
  90. data/ext/cumo/narray/gen/tmpl_bit/aref.c +2 -2
  91. data/ext/cumo/narray/gen/tmpl_bit/aref_cpu.c +2 -2
  92. data/ext/cumo/narray/gen/tmpl_bit/aset.c +3 -3
  93. data/ext/cumo/narray/gen/tmpl_bit/binary.c +2 -2
  94. data/ext/cumo/narray/gen/tmpl_bit/bit_count.c +3 -3
  95. data/ext/cumo/narray/gen/tmpl_bit/bit_count_cpu.c +3 -3
  96. data/ext/cumo/narray/gen/tmpl_bit/bit_reduce.c +3 -3
  97. data/ext/cumo/narray/gen/tmpl_bit/bit_stat.c +3 -3
  98. data/ext/cumo/narray/gen/tmpl_bit/each.c +1 -1
  99. data/ext/cumo/narray/gen/tmpl_bit/each_with_index.c +1 -1
  100. data/ext/cumo/narray/gen/tmpl_bit/extract.c +1 -1
  101. data/ext/cumo/narray/gen/tmpl_bit/extract_cpu.c +1 -1
  102. data/ext/cumo/narray/gen/tmpl_bit/fill.c +2 -2
  103. data/ext/cumo/narray/gen/tmpl_bit/format.c +2 -2
  104. data/ext/cumo/narray/gen/tmpl_bit/format_to_a.c +2 -2
  105. data/ext/cumo/narray/gen/tmpl_bit/inspect.c +1 -1
  106. data/ext/cumo/narray/gen/tmpl_bit/mask.c +2 -2
  107. data/ext/cumo/narray/gen/tmpl_bit/store_array.c +3 -2
  108. data/ext/cumo/narray/gen/tmpl_bit/to_a.c +1 -1
  109. data/ext/cumo/narray/gen/tmpl_bit/unary.c +1 -1
  110. data/ext/cumo/narray/gen/tmpl_bit/where.c +1 -1
  111. data/ext/cumo/narray/gen/tmpl_bit/where2.c +1 -1
  112. data/ext/cumo/narray/index.c +7 -4
  113. data/ext/cumo/narray/math.c +6 -6
  114. data/ext/cumo/narray/narray.c +57 -45
  115. data/ext/cumo/narray/ndloop.c +7 -7
  116. data/ext/cumo/narray/rand.c +7 -12
  117. data/ext/cumo/narray/struct.c +3 -3
  118. data/lib/cumo/cuda/compiler.rb +1 -1
  119. data/lib/cumo/cuda/elementwise_kernel.rb +7 -6
  120. data/lib/cumo/cuda/reduction_kernel.rb +7 -4
  121. data/lib/cumo/cuda/user_kernel.rb +5 -2
  122. data/lib/cumo/linalg.rb +1639 -0
  123. data/lib/cumo/narray/extra.rb +13 -11
  124. data/lib/cumo.rb +2 -0
  125. metadata +9 -75
  126. data/.gitignore +0 -32
  127. data/.rubocop.yml +0 -15
  128. data/.rubocop_todo.yml +0 -1691
  129. data/Dockerfile +0 -34
  130. data/Gemfile +0 -14
  131. data/bench/bench.rb +0 -235
  132. data/bench/broadcast_fp32.rb +0 -140
  133. data/bench/cg_bench.rb +0 -271
  134. data/bench/crossover_bench.rb +0 -374
  135. data/bench/cumo_bench.rb +0 -218
  136. data/bench/cumo_probe.rb +0 -795
  137. data/bench/cumo_shape_probe.rb +0 -264
  138. data/bench/cumo_sync_probe.rb +0 -505
  139. data/bench/ising_bench.rb +0 -242
  140. data/bench/kmeans_bench.rb +0 -254
  141. data/bench/numo_bench.rb +0 -142
  142. data/bench/particle_bench.rb +0 -261
  143. data/bench/reduction_fp32.rb +0 -119
  144. data/bench/transformer_bench.rb +0 -264
  145. data/bin/console +0 -15
  146. data/bin/setup +0 -8
  147. data/docker-build.sh +0 -4
  148. data/docker-launch.sh +0 -4
  149. data/test/bfloat_test.rb +0 -238
  150. data/test/bit_test.rb +0 -1237
  151. data/test/cuda/compiler_test.rb +0 -153
  152. data/test/cuda/device_test.rb +0 -73
  153. data/test/cuda/driver_test.rb +0 -108
  154. data/test/cuda/elementwise_kernel_test.rb +0 -305
  155. data/test/cuda/function_test.rb +0 -162
  156. data/test/cuda/memory_pool_test.rb +0 -179
  157. data/test/cuda/module_test.rb +0 -15
  158. data/test/cuda/name_expression_test.rb +0 -172
  159. data/test/cuda/nvrtc_test.rb +0 -119
  160. data/test/cuda/pinned_memory_test.rb +0 -138
  161. data/test/cuda/reduction_kernel_test.rb +0 -276
  162. data/test/cuda/runtime_test.rb +0 -74
  163. data/test/cuda/stream_test.rb +0 -247
  164. data/test/cudnn_test.rb +0 -1008
  165. data/test/cumo_test.rb +0 -169
  166. data/test/extra_test.rb +0 -1209
  167. data/test/fused_test.rb +0 -608
  168. data/test/hfloat_test.rb +0 -717
  169. data/test/math_test.rb +0 -983
  170. data/test/narray_alt_coverage_test.rb +0 -980
  171. data/test/narray_test.rb +0 -8924
  172. data/test/ractor_test.rb +0 -54
  173. data/test/test_helper.rb +0 -105
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d50aabaf6d47d63e97fc6c68726cac94af2ecb8b737a3441de618860d2f5ab6a
4
- data.tar.gz: 9d7bfc02015586d0ef533802be1d48558abf93fddf362ce445c55b0b4713ab7b
3
+ metadata.gz: ad04bee56b44e50dfc5df236d806a034f1adef98415e6e78ffcdd73546047dfd
4
+ data.tar.gz: c202fc43967dc292dddeea61274b89be6095f8724a7cfb7ac37d299cbc56cc7b
5
5
  SHA512:
6
- metadata.gz: 99f47260f8dbbb9320d2f74984262e328fd42c2dac0321300b6087712dd129d03f80d65a2089f12f624ea2814e59eb8400748c9bf08f3937b09c0bceb452f417
7
- data.tar.gz: a3ccc77b320c0bfdaa64abed339cc22ce5099638f8a52e9abc5a44a1e144295f56581b43ebf99f5a431d54bbe7bed2fe590975abd59cb6b7e1a9008878c91f23
6
+ metadata.gz: 00b7285a170c241c968f2bba0ab4e80013f971c01964d8d092eefa4eb177ea06d68616326bf457b1e9a6a9c08ca8906b85456ba2ef91acbb13334292959b10a0
7
+ data.tar.gz: eba6aed6cdb916d1abee4d856a5496892ef5e33d3bb55cf4124fb01c3066a0324301baefe5d095798e21a0066af5d87c33ceca51142a00fec24936450b9f5946
data/.yardopts ADDED
@@ -0,0 +1,10 @@
1
+ --no-private
2
+ --exclude ext/cumo/narray/gen/
3
+ -e ext/cumo/narray/gen/cogen_runner.rb
4
+ lib/**/*.rb
5
+ ext/**/*.c
6
+ ext/**/*.cpp
7
+ tmp/yard/types/*.c
8
+ -
9
+ *.md
10
+ LICENSE.txt
@@ -38,7 +38,7 @@ module MakeMakefileCuda
38
38
  # TODO(sonots): Make it possible to configure "nvcc" and additional arguments
39
39
  def nvcc_command
40
40
  s = MakeMakefileCuda::Nvcc.generate(argv)
41
- cmd = "nvcc #{s}"
41
+ cmd = "nvcc #{s} -Xfatbin=-compress-all"
42
42
  if ENV['CUMO_NVCC_GENERATE_CODE']
43
43
  ENV['CUMO_NVCC_GENERATE_CODE'].split.each do |code|
44
44
  cmd << " --generate-code=#{code}"
data/CHANGELOG.md CHANGED
@@ -1,3 +1,51 @@
1
+ # 0.13.0 (2026/10/12)
2
+
3
+ Breaking changes:
4
+
5
+ * An integer array raised to a negative integer power raises `Cumo::NArray::ValueError`, where it answered 0 even for a base of 1 or -1. numo-narray-alt now raises here too, as NumPy does. A signed integer exponent held in an array is checked on the device, so such a `**` now waits for its kernel, as integer division by an array does. An exponent array that holds `2**31` or more is no longer narrowed to 32 bits, so `Cumo::Int64[1] ** Cumo::Int64[2**31]` answers `[1]` where it answered `[0]` (PR #614)
6
+
7
+ Fixes:
8
+
9
+ * Fix `inspect` printing `(empty)` for a scalar cast to an array, such as `Cumo::DFloat.cast(5.0)`, and for its views, which it did since 0.5.10. Its value and the operators on it were right. Fix such an array that is frozen, as `Ractor.make_shareable` leaves it, raising `FrozenError` when read, and then answering 0 to some operators and its value to others (PR #616)
10
+ * Fix a crash, or a read of the wrong memory, when a 0-dimensional array is indexed with nothing but `false`, `:rest` or `:~`, such as `Cumo::DFloat.cast(5.0)[false]` (PR #615)
11
+ * Fix the cuDNN algorithm caches and the counters that deferred host reads and index fills wait on, which two Ractors could update at once. They now take a lock or advance atomically (PR #613)
12
+ * Fix two Ractors calling `rand` or `rand_norm` at the same time drawing the same numbers. A single thread draws what it drew before, so `srand` still reproduces a sequence (PR #612)
13
+ * Fix wrong results, an illegal memory access, or a process stuck at full GPU load when two Ractors call cuSOLVER at once, as `Cumo::Linalg.svdvals` and `eigvals` do. Each thread keeps its own workspace and frees it when it ends (PR #611)
14
+ * Fix `Cumo::Linalg.expm` of a triangular matrix whose elements off the diagonal are far larger than those on it answering 1 on the diagonal. The diagonal and the first off-diagonal are now reset to their exact values after each squaring, as scipy does (PR #609)
15
+ * Fix `Cumo::Linalg.expm` raising `FloatDomainError: Infinity` for a finite matrix whose 1-norm overflows. It still refuses such a matrix, and now says that its 1-norm overflowed (PR #603)
16
+ * Fix `Cumo::Linalg.lu` and `matrix_power(a, 0)` raising `NoMethodError` for a Bit matrix. `P` and the identity are Bit matrices (PR #602)
17
+ * Fix `Cumo::Linalg.lu` raising `ShapeError` for an empty matrix. It answers `P`, `L` and `U` of m x m, m x 0 and 0 x n, the shapes a nonempty matrix gets. numo-linalg-alt raises here (PR #601)
18
+
19
+ Changes:
20
+
21
+ * The first elementwise call on a type loads the kernels of one family rather than every elementwise kernel of the type. The families are arithmetic, common math functions, rare math functions, comparisons, stores from other types, and random numbers with polynomials. A first SFloat add takes 28 MB of host memory where it took 86 MB (PR #610)
22
+ * `require "cumo"` no longer loads NVRTC. It is loaded from the CUDA that cumo was built against the first time `ElementwiseKernel`, `ReductionKernel` or `RawKernel` builds a kernel, which takes about 34 MB off the RSS after `require` (PR #607, PR #606)
23
+ * `Cumo::Linalg.norm` of ord 1, -1, Infinity and -Infinity takes the magnitudes as it reduces rather than through `abs`, which makes it about 2.4 times faster on a 4096 x 4096 DFloat (PR #605)
24
+ * `Cumo::Linalg.norm` of ord 2, -2 and `"nuc"` over stacked matrices of up to 32 rows and columns computes them in one batched call, about 7 times faster for 1000 DFloat matrices of 8 x 8. A stacked matrix that holds NaN or Infinity answers NaN, as the matrices past 32 do (PR #604)
25
+ * `Cumo::Linalg.expm` of a small matrix reads its three norms in one wait, which makes it about 3% faster on a 64 x 64 DFloat (PR #600)
26
+ * The factorizations in `Cumo::Linalg` free their column-major copies once used, so a small call reuses memory the GPU has touched: `lu_solve` of a 256 x 256 SFloat is about 8% faster (PR #599)
27
+ * `repeat` builds its index in one Array: `repeat(2)` of a million elements is 3.4 times faster, and `repeat` with an Array of counts 2.2 times. The development setup checks `lib` with rubocop-performance (PR #598, PR #597)
28
+ * `Cumo::Linalg.eig` and `svd` give cuSOLVER pinned host memory to work in, kept for the process up to 256 MiB: `eig` of a 4096 x 4096 SFloat is about 8% faster, and `svd` of an 8192 x 2048 SFloat about 5% (PR #596)
29
+ * `Cumo::Linalg.expm` frees its temporary matrices as it goes rather than leaving about 40 a call to the garbage collector, which makes it about 15% faster on a 1024 x 1024 SFloat of large norm (PR #595)
30
+ * The API docs show the parameters of overloaded methods such as `cast`, `store_binary` and `layer_norm`, where YARD warned 1,419 times (PR #608)
31
+
32
+ # 0.12.0 (2026/10/04)
33
+
34
+ Fixes:
35
+
36
+ * Fix a stream left current in a thread that ended never being destroyed. Neither `Stream#destroy` nor the finalizer could free it, so about 0.5 MiB of device memory stayed behind per such stream, and under M:N threads the next Ruby thread on the same native thread inherited it. The current stream now lives on the Ruby `Thread`, which adds about 2% to the fixed cost of a launch (PR #593)
37
+ * Fix a device memory leak each time a thread that used cuBLAS, cuSOLVER or cuDNN ends: 64 MiB per thread for cuBLAS, 32 MiB more for cuSOLVER and about 8 MiB for cuDNN. An ending thread now leaves its handles to the next one, so device memory stays at what the most threads alive at once needed (PR #591)
38
+ * Fix `x.inplace.cumsum` and `cumprod` answering a new array and leaving `x` as it was, which they did since 0.10.0 (PR #582)
39
+
40
+ Changes:
41
+
42
+ * Add `Cumo::Linalg`, the functions of `Numo::Linalg` from numo-linalg-alt 0.10.1 on cuSOLVER and cuBLAS, with the same names, arguments, return values and errors: `blas_char`, `dot`, `matmul`, `matrix_power`, `solve`, `inv`, `det`, `slogdet`, `lu`, `lu_fact`, `lu_solve`, `lu_inv`, `cholesky`, `cho_fact`, `cho_solve`, `cho_inv`, `ldl`, `qr`, `eig`, `eigvals`, `eigh`, `eigvalsh`, `svd`, `svdvals`, `pinv`, `lstsq`, `matrix_rank`, `orth`, `null_space`, `norm`, `cond` and `expm`. Load it with `require "cumo/linalg"`. Cumo builds it when the toolkit has cuSOLVER's 64-bit API, and `Cumo::CUDA::Cusolver.available?` says whether it did. `eig` and `eigvals` need `cusolverDnXgeev`, which CUDA 11 does not have, and `ldl` of a Hermitian complex matrix needs `cusolverDnXhetrf`, which CUDA 11 and 12 do not have. Where it differs from numo-linalg-alt on purpose, as in `expm`, `norm` and `cond`, the README says how (PR #594, PR #592, PR #589, PR #588, PR #587, PR #586, PR #584, PR #581, PR #580, PR #577, PR #576, PR #575, PR #574, PR #573, PR #572, PR #571, PR #570, PR #569, PR #568, PR #567)
43
+ * `require "cumo"` sets `CUDA_MODULE_LOADING` to `LAZY` unless it is set already, as PyTorch does. Drivers before 535 otherwise load every kernel module when CUDA starts, which adds about 2.2 GB of RSS. Child processes inherit the setting (PR #590)
44
+ * `triu`, `tril`, `triu!` and `tril!` clear the triangle of a contiguous array with an Integer `k` in one kernel rather than through an index array: `triu` of a 16 x 16 DFloat takes 0.006 ms where it took 0.49 (PR #585)
45
+ * The first kernel of a type costs less host memory. Each type builds separate modules for its reductions, their nan-aware forms and `mulsum` against another type, so DFloat adds 88 MB of RSS for elementwise work and 137 MB once it reduces, where its first kernel of any kind added 181 MB. Every fatbin image is compressed, which takes `cumo.so` from 120 MB to 112 MB (PR #583)
46
+ * The API docs show the NArray methods defined in C, such as `shape` and `NArray.zeros`, and keep the docstrings of the typed-class methods. `.yardopts` keeps the build-time code generators out of the docs, and the gemspec names rubydoc.info as the documentation (PR #579, PR #578)
47
+ * The gem no longer ships the test suite, `bench/` and the files for working on cumo (PR #566)
48
+
1
49
  # 0.11.0 (2026/09/27)
2
50
 
3
51
  Breaking changes:
@@ -486,7 +534,7 @@ Fixes:
486
534
  * Remove unnecessary debug code
487
535
  * Fix capability list
488
536
  * Build only with supported capabilities to reduce compilation time
489
- * Fix SEGV when calling {mean, var, stddev, rms} on a single-element array (#154)
537
+ * Fix SEGV when calling `mean`, `var`, `stddev` or `rms` on a single-element array (#154)
490
538
  * Suppress warning message for deprecated declarations
491
539
  * Fix variable typo in complex log2 and log10 functions (#152)
492
540
 
@@ -528,7 +576,7 @@ Fixes:
528
576
  * Backport: fix macro: STORE_BIT STORE_BIT_STEP: requires mask to leave the lowest bit
529
577
  * Backport: fix NArray::Bit#any?,all?: empty array should return false
530
578
  * Backport: fix NArray::Bit#count_true/false: empty array should return zero
531
- * Backport: bug in NArray::Bit; fix bit operation in tmpl_bit/{store_bit,unary,binary}.c
579
+ * Backport: bug in NArray::Bit; fix bit operation in `tmpl_bit/{store_bit,unary,binary}.c`
532
580
  * Fix typo
533
581
  * Backport 135: Make all empty arrays equal
534
582
  * Backport: minor fixes in na_get_result_dimension(), check_index_count()
data/README.md CHANGED
@@ -10,6 +10,7 @@ Cumo (pronounced "koomo") is a CUDA-aware, GPU-optimized numerical library that
10
10
  * NVIDIA GPU Compute Capability 3.5 (Kepler) or later
11
11
  * CUDA 11.0 or later
12
12
  * cuDNN 8.0 or later (optional, for the cuDNN features)
13
+ * cuSOLVER with its 64-bit API, which comes with the CUDA Toolkit (optional, for `Cumo::Linalg`)
13
14
 
14
15
  ## Preparation
15
16
 
@@ -282,6 +283,41 @@ q.dot(kt)
282
283
  All of this is about the two-dimensional case, where cuBLAS is given one matrix.
283
284
  A batched multiplication takes another path through the same flag.
284
285
 
286
+ ### Linear Algebra
287
+
288
+ `require "cumo/linalg"` brings in `Cumo::Linalg`, the functions of `Numo::Linalg` from [numo-linalg-alt](https://github.com/yoshoku/numo-linalg-alt) on the GPU.
289
+ They keep numo-linalg-alt's names, arguments, return values and errors, and run on cuSOLVER and cuBLAS.
290
+
291
+ ```ruby
292
+ require "cumo/linalg"
293
+
294
+ a = Cumo::DFloat.new(3, 3).rand + Cumo::DFloat.eye(3) * 3
295
+ b = Cumo::DFloat.new(3).rand
296
+ x = Cumo::Linalg.solve(a, b)
297
+ w, v = Cumo::Linalg.eigh(a + a.transpose)
298
+ ```
299
+
300
+ The functions are `blas_char`, `dot`, `matmul`, `matrix_power`, `solve`, `inv`, `det`, `slogdet`, `lu`, `lu_fact`, `lu_solve`, `lu_inv`, `cholesky`, `cho_fact`, `cho_solve`, `cho_inv`, `ldl`, `qr`, `eig`, `eigvals`, `eigh`, `eigvalsh`, `svd`, `svdvals`, `pinv`, `lstsq`, `matrix_rank`, `orth`, `null_space`, `norm`, `cond` and `expm`.
301
+ They take `SFloat`, `DFloat`, `SComplex` and `DComplex`.
302
+ An integer or `Bit` matrix is computed in `DFloat`, as numo-linalg-alt does, and `HFloat` and `BFloat` raise `TypeError`.
303
+
304
+ Cumo looks for cuSOLVER when it is built.
305
+ Without it, the functions that need it raise `NotImplementedError`, and `Cumo::CUDA::Cusolver.available?` answers whether it is there.
306
+ `eig` and `eigvals` also need `cusolverDnXgeev`, which CUDA 11 does not have.
307
+ `ldl` of a Hermitian complex matrix needs `cusolverDnXhetrf`, which CUDA 11 and 12 do not have.
308
+
309
+ Cumo differs from numo-linalg-alt in a few places on purpose:
310
+
311
+ * Where numo-linalg-alt answers wrong, Cumo answers as numpy does: a wide matrix in `orth` and `lstsq`, a zero matrix in `pinv`, a matrix of full rank in `null_space`, an integer matrix in `qr` and `svd`, and empty matrices.
312
+ * `expm` scales and squares the way scipy does, after Al-Mohy and Higham, which stays accurate where the norm of the matrix is large. Passing the order of the Pade approximant, as in `expm(a, 3)`, keeps numo-linalg-alt's algorithm.
313
+ * `norm` takes the 2-norm and the nuclear norm of matrices stacked along other axes, which numo-linalg-alt refuses.
314
+ * `cond` checks `ord` before it inverts the matrix, so an invalid `ord` raises `ArgumentError` even for a singular matrix, where numo-linalg-alt raises `LapackError`.
315
+ * `lu` answers for an empty matrix and for a `Bit` matrix, where numo-linalg-alt raises. P keeps the shape and the class it has for any other matrix: an m x m identity for an m x 0 matrix, where scipy answers a 0 x 0 one, and a `Bit` for a `Bit` matrix. `matrix_power(a, 0)` of a `Bit` matrix likewise answers a `Bit` identity.
316
+
317
+ A function waits for the current stream when it answers a Ruby number, or when it reads back whether cuSOLVER could do its work.
318
+ `solve`, `inv` and `det` read back whether the matrix was singular, `cho_fact` whether it was positive definite, and `eigh` and `svd` whether they converged.
319
+ `matmul`, `dot` of two matrices, `matrix_power` to a positive power, `cholesky`, `cho_solve`, `qr` and `norm` along an axis wait for nothing, so a run of them is queued without the host waiting.
320
+
285
321
  ### Fused Operations
286
322
 
287
323
  `layer_norm`, `rms_norm` and `softmax` normalize along the last axis in one kernel each, and `quantize_symmetric` takes it to 8-bit integers in one more.
@@ -962,6 +998,7 @@ export CUMO_ALLOW_TF32=1
962
998
  Over the convolutions of a ResNet-18 forward pass at batch 16, with the ceiling raised to 256MB, the pass gets faster and the worst layer moves from 1.4e-05 to 2.4e-04 against a double precision reference.
963
999
 
964
1000
  The same flag puts `SFloat` and `SComplex` `gemm` on the tensor cores as TF32, and `dot` where it goes through `gemm`.
1001
+ `Cumo::Linalg.matrix_power` and `Cumo::Linalg.expm` multiply through `gemm`, so the flag reaches them too.
965
1002
  Off, the answer is the one cuBLAS gives at single precision, bit for bit.
966
1003
  On, a large `SFloat` `gemm` runs faster, and its answer is then about 3e-04 from a double precision reference, where it was 4e-07.
967
1004
  The double types are not affected either way.
data/cumo.gemspec CHANGED
@@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
18
18
 
19
19
  spec.metadata["homepage_uri"] = spec.homepage
20
20
  spec.metadata["source_code_uri"] = spec.homepage
21
+ spec.metadata["documentation_uri"] = "https://rubydoc.info/gems/cumo/"
21
22
  spec.metadata["changelog_uri"] = "https://github.com/sonots/cumo/blob/master/CHANGELOG.md"
22
23
  spec.metadata["bug_tracker_uri"] = "https://github.com/sonots/cumo/issues"
23
24
  spec.metadata["rubygems_mfa_required"] = "true"
@@ -25,9 +26,8 @@ Gem::Specification.new do |spec|
25
26
  spec.required_ruby_version = ">= 3.0.0"
26
27
 
27
28
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
28
- f.match(%r{^(.github|test|spec|features|docker|examples)/})
29
+ f.start_with?(*%w[.github/ test/ examples/ bin/ bench/ docker- Dockerfile Gemfile .gitignore .rubocop])
29
30
  end
30
- spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
31
31
  spec.bindir = "exe"
32
32
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
33
  spec.require_paths = ["lib"]
@@ -43,31 +43,22 @@ cumo_cuda_cublas_check_status(cublasStatus_t status)
43
43
  }
44
44
  }
45
45
 
46
- // Lazily initialize cublas handle, and cache it
46
+ static cumo_cuda_thread_local_t handles;
47
+
48
+ // One handle per thread: the stream is set on the handle before each call,
49
+ // and another thread with another current stream must not reset it in
50
+ // between. A thread that ends leaves its handle to the next one.
47
51
  cublasHandle_t
48
52
  cumo_cuda_cublas_handle()
49
53
  {
50
- // One table per thread: the stream is set on the handle before each call,
51
- // and another thread with another current stream must not reset it in
52
- // between. A handle is never destroyed.
53
- static __thread cublasHandle_t *handles = 0;
54
- int device;
55
- if (handles == 0) {
56
- int i;
57
- int device_count = cumo_cuda_runtime_get_device_count();
58
- handles = ALLOC_N(cublasHandle_t, device_count);
59
- for (i = 0; i < device_count; ++i) {
60
- handles[i] = 0;
61
- }
62
- }
63
- device = cumo_cuda_runtime_get_device();
64
- if (handles[device] == 0) {
54
+ cublasHandle_t *handle = (cublasHandle_t*)cumo_cuda_thread_local_get(&handles);
55
+ if (*handle == 0) {
65
56
  // A discarded status leaves the handle NULL, and cuBLAS reports that as
66
57
  // CUBLAS_STATUS_NOT_INITIALIZED at the next call instead of the reason.
67
- cumo_cuda_cublas_check_status(cublasCreate(&handles[device]));
58
+ cumo_cuda_cublas_check_status(cublasCreate(handle));
68
59
  }
69
- cumo_cuda_cublas_check_status(cublasSetStream(handles[device], cumo_cuda_stream()));
70
- return handles[device];
60
+ cumo_cuda_cublas_check_status(cublasSetStream(*handle, cumo_cuda_stream()));
61
+ return *handle;
71
62
  }
72
63
 
73
64
  VALUE
@@ -88,6 +79,8 @@ Init_cumo_cuda_cublas(void)
88
79
  VALUE mCumo = rb_define_module("Cumo");
89
80
  VALUE mCUDA = rb_define_module_under(mCumo, "CUDA");
90
81
 
82
+ cumo_cuda_thread_local_init(&handles, sizeof(cublasHandle_t));
83
+
91
84
  /*
92
85
  Document-module: Cumo::Cublas
93
86
  */
@@ -72,30 +72,21 @@ cumo_cuda_cudnn_release_conv_held(VALUE held)
72
72
  return Qnil;
73
73
  }
74
74
 
75
- // Lazily initialize cudnn handle, and cache it
75
+ static cumo_cuda_thread_local_t handles;
76
+
77
+ // One handle per thread, as in cublas.c: the stream set on the handle must
78
+ // not be reset by another thread before the call.
76
79
  cudnnHandle_t
77
80
  cumo_cuda_cudnn_handle()
78
81
  {
79
- // One table per thread, as in cublas.c: the stream set on the handle must
80
- // not be reset by another thread before the call. A handle is never destroyed.
81
- static __thread cudnnHandle_t *handles = 0;
82
- int device;
83
- if (handles == 0) {
84
- int i;
85
- int device_count = cumo_cuda_runtime_get_device_count();
86
- handles = ALLOC_N(cudnnHandle_t, device_count);
87
- for (i = 0; i < device_count; ++i) {
88
- handles[i] = 0;
89
- }
90
- }
91
- device = cumo_cuda_runtime_get_device();
92
- if (handles[device] == 0) {
82
+ cudnnHandle_t *handle = (cudnnHandle_t*)cumo_cuda_thread_local_get(&handles);
83
+ if (*handle == 0) {
93
84
  // A discarded status leaves the handle NULL, and cuDNN reports that as
94
85
  // CUDNN_STATUS_NOT_INITIALIZED at the next call instead of the reason.
95
- cumo_cuda_cudnn_check_status(cudnnCreate(&handles[device]));
86
+ cumo_cuda_cudnn_check_status(cudnnCreate(handle));
96
87
  }
97
- cumo_cuda_cudnn_check_status(cudnnSetStream(handles[device], cumo_cuda_stream()));
98
- return handles[device];
88
+ cumo_cuda_cudnn_check_status(cudnnSetStream(*handle, cumo_cuda_stream()));
89
+ return *handle;
99
90
  }
100
91
 
101
92
  #endif // CUDNN_FOUND
@@ -144,6 +135,7 @@ Init_cumo_cuda_cudnn(void)
144
135
 
145
136
  rb_define_singleton_method(mCUDNN, "available?", rb_cudnn_available_p, 0);
146
137
  #ifdef CUDNN_FOUND
138
+ cumo_cuda_thread_local_init(&handles, sizeof(cudnnHandle_t));
147
139
  init_max_workspace_size();
148
140
  rb_define_singleton_method(mCUDNN, "max_workspace_size", rb_cudnn_max_workspace_size, 0);
149
141
  rb_define_const(mCUDNN, "CUDNN_POOLING_MAX", INT2NUM(CUDNN_POOLING_MAX));
@@ -11,6 +11,7 @@
11
11
  #include "cumo/cuda/runtime.h"
12
12
  #include "cumo/cuda/memory_pool.h"
13
13
 
14
+ #include <mutex>
14
15
  #include <unordered_map>
15
16
 
16
17
  #if defined(__cplusplus)
@@ -387,13 +388,32 @@ struct AlgoCacheEntry {
387
388
  // Nothing may reach the C caller: it has no handler, so an escaping exception
388
389
  // is std::terminate. The cache only saves a later search, so one that cannot
389
390
  // grow leaves the answer this call already has.
391
+ static std::mutex algo_cache_mutex_;
392
+
390
393
  template <typename Map>
391
394
  void StoreAlgoCache(Map& map, const AlgoCacheKey& key, const typename Map::mapped_type& entry) {
395
+ std::lock_guard<std::mutex> lock{algo_cache_mutex_};
392
396
  try {
393
397
  map[key] = entry;
394
398
  } catch (...) {
395
399
  }
396
400
  }
401
+
402
+ template <typename Map, typename Perf>
403
+ bool LookupAlgoCache(Map& map, const AlgoCacheKey& key, Perf* perf_result) {
404
+ std::lock_guard<std::mutex> lock{algo_cache_mutex_};
405
+ auto it = map.find(key);
406
+ if (it == map.end()) {
407
+ return false;
408
+ }
409
+ auto entry = it->second;
410
+ // clear the fields the search would have filled but the cache drops
411
+ *perf_result = {};
412
+ perf_result->algo = entry.algo;
413
+ perf_result->memory = entry.memory;
414
+ perf_result->mathType = entry.math_type;
415
+ return true;
416
+ }
397
417
  }
398
418
 
399
419
  using FwdAlgoCacheMap = std::unordered_map<AlgoCacheKey, AlgoCacheEntry<cudnnConvolutionFwdAlgo_t>, AlgoCacheKeyHash>;
@@ -456,15 +476,7 @@ cumo_cuda_cudnn_FindConvolutionForwardAlgorithm(
456
476
  cudnn_dtype, max_workspace_size);
457
477
 
458
478
  auto& algo_cache_map = fwd_algo_cache_map_;
459
- // TODO: thread-safe
460
- auto it = algo_cache_map.find(key);
461
- if (it != algo_cache_map.end()) {
462
- auto entry = it->second;
463
- // clear the fields the search would have filled but the cache drops
464
- *perf_result = {};
465
- perf_result->algo = entry.algo;
466
- perf_result->memory = entry.memory;
467
- perf_result->mathType = entry.math_type;
479
+ if (LookupAlgoCache(algo_cache_map, key, perf_result)) {
468
480
  return CUDNN_STATUS_SUCCESS;
469
481
  }
470
482
 
@@ -495,7 +507,6 @@ cumo_cuda_cudnn_FindConvolutionForwardAlgorithm(
495
507
  if (returned_algo_count < 1) return CUDNN_STATUS_NOT_SUPPORTED;
496
508
  if (perf_result->status != CUDNN_STATUS_SUCCESS) return perf_result->status;
497
509
 
498
- // TODO: thread-safe
499
510
  StoreAlgoCache(algo_cache_map, key, {perf_result->algo, perf_result->memory, perf_result->mathType});
500
511
  return status;
501
512
  }
@@ -527,15 +538,7 @@ cumo_cuda_cudnn_FindConvolutionBackwardDataAlgorithm(
527
538
  cudnn_dtype, max_workspace_size);
528
539
 
529
540
  auto& algo_cache_map = bwd_data_algo_cache_map_;
530
- // TODO: thread-safe
531
- auto it = algo_cache_map.find(key);
532
- if (it != algo_cache_map.end()) {
533
- auto entry = it->second;
534
- // clear the fields the search would have filled but the cache drops
535
- *perf_result = {};
536
- perf_result->algo = entry.algo;
537
- perf_result->memory = entry.memory;
538
- perf_result->mathType = entry.math_type;
541
+ if (LookupAlgoCache(algo_cache_map, key, perf_result)) {
539
542
  return CUDNN_STATUS_SUCCESS;
540
543
  }
541
544
 
@@ -566,7 +569,6 @@ cumo_cuda_cudnn_FindConvolutionBackwardDataAlgorithm(
566
569
  if (returned_algo_count < 1) return CUDNN_STATUS_NOT_SUPPORTED;
567
570
  if (perf_result->status != CUDNN_STATUS_SUCCESS) return perf_result->status;
568
571
 
569
- // TODO: thread-safe
570
572
  StoreAlgoCache(algo_cache_map, key, {perf_result->algo, perf_result->memory, perf_result->mathType});
571
573
  return status;
572
574
  }
@@ -598,15 +600,7 @@ cumo_cuda_cudnn_FindConvolutionBackwardFilterAlgorithm(
598
600
  cudnn_dtype, max_workspace_size);
599
601
 
600
602
  auto& algo_cache_map = bwd_filter_algo_cache_map_;
601
- // TODO: thread-safe
602
- auto it = algo_cache_map.find(key);
603
- if (it != algo_cache_map.end()) {
604
- auto entry = it->second;
605
- // clear the fields the search would have filled but the cache drops
606
- *perf_result = {};
607
- perf_result->algo = entry.algo;
608
- perf_result->memory = entry.memory;
609
- perf_result->mathType = entry.math_type;
603
+ if (LookupAlgoCache(algo_cache_map, key, perf_result)) {
610
604
  return CUDNN_STATUS_SUCCESS;
611
605
  }
612
606
 
@@ -637,7 +631,6 @@ cumo_cuda_cudnn_FindConvolutionBackwardFilterAlgorithm(
637
631
  if (returned_algo_count < 1) return CUDNN_STATUS_NOT_SUPPORTED;
638
632
  if (perf_result->status != CUDNN_STATUS_SUCCESS) return perf_result->status;
639
633
 
640
- // TODO: thread-safe
641
634
  StoreAlgoCache(algo_cache_map, key, {perf_result->algo, perf_result->memory, perf_result->mathType});
642
635
  return status;
643
636
  }