cumo 0.12.0 → 0.14.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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -2
  3. data/README.md +51 -0
  4. data/cumo.gemspec +10 -1
  5. data/ext/cumo/cuda/cudnn_impl.cpp +23 -30
  6. data/ext/cumo/cuda/cufft.c +605 -0
  7. data/ext/cumo/cuda/cusolver.c +193 -24
  8. data/ext/cumo/cuda/driver.c +1 -0
  9. data/ext/cumo/cuda/memory_pool.cpp +2 -0
  10. data/ext/cumo/cuda/nvrtc.c +77 -11
  11. data/ext/cumo/cuda/runtime.c +15 -4
  12. data/ext/cumo/cumo.c +2 -2
  13. data/ext/cumo/depend.erb +1 -1
  14. data/ext/cumo/extconf.rb +10 -2
  15. data/ext/cumo/include/cumo/atomic.h +27 -0
  16. data/ext/cumo/include/cumo/cuda/cufft.h +38 -0
  17. data/ext/cumo/include/cumo/cuda/runtime.h +26 -5
  18. data/ext/cumo/include/cumo/intern.h +2 -2
  19. data/ext/cumo/include/cumo/narray.h +2 -2
  20. data/ext/cumo/include/cumo/types/int_macro_kernel.h +1 -1
  21. data/ext/cumo/include/cumo/types/uint_macro_kernel.h +1 -2
  22. data/ext/cumo/include/cumo.h +0 -3
  23. data/ext/cumo/narray/array.c +4 -4
  24. data/ext/cumo/narray/data.c +14 -16
  25. data/ext/cumo/narray/gen/cogen_kernel.rb +24 -2
  26. data/ext/cumo/narray/gen/erbpp2.rb +7 -1
  27. data/ext/cumo/narray/gen/kernel_parts.rb +11 -1
  28. data/ext/cumo/narray/gen/narray_def.rb +2 -2
  29. data/ext/cumo/narray/gen/spec.rb +10 -0
  30. data/ext/cumo/narray/gen/tmpl/accum.c +9 -8
  31. data/ext/cumo/narray/gen/tmpl/accum_arg.c +3 -3
  32. data/ext/cumo/narray/gen/tmpl/accum_binary.c +5 -5
  33. data/ext/cumo/narray/gen/tmpl/accum_index.c +3 -3
  34. data/ext/cumo/narray/gen/tmpl/allocate.c +3 -1
  35. data/ext/cumo/narray/gen/tmpl/aref.c +2 -2
  36. data/ext/cumo/narray/gen/tmpl/aref_cpu.c +2 -2
  37. data/ext/cumo/narray/gen/tmpl/aset.c +3 -3
  38. data/ext/cumo/narray/gen/tmpl/at.c +2 -2
  39. data/ext/cumo/narray/gen/tmpl/binary.c +2 -2
  40. data/ext/cumo/narray/gen/tmpl/binary2.c +2 -2
  41. data/ext/cumo/narray/gen/tmpl/binary_s.c +3 -3
  42. data/ext/cumo/narray/gen/tmpl/bincount.c +7 -7
  43. data/ext/cumo/narray/gen/tmpl/cast.c +4 -2
  44. data/ext/cumo/narray/gen/tmpl/clip.c +3 -3
  45. data/ext/cumo/narray/gen/tmpl/coerce_cast.c +1 -1
  46. data/ext/cumo/narray/gen/tmpl/complex_accum_kernel.cu +44 -0
  47. data/ext/cumo/narray/gen/tmpl/cond_binary.c +2 -2
  48. data/ext/cumo/narray/gen/tmpl/cond_unary.c +1 -1
  49. data/ext/cumo/narray/gen/tmpl/cum.c +3 -3
  50. data/ext/cumo/narray/gen/tmpl/each.c +1 -1
  51. data/ext/cumo/narray/gen/tmpl/ewcomp.c +6 -5
  52. data/ext/cumo/narray/gen/tmpl/extract.c +1 -1
  53. data/ext/cumo/narray/gen/tmpl/extract_cpu.c +1 -1
  54. data/ext/cumo/narray/gen/tmpl/eye.c +5 -5
  55. data/ext/cumo/narray/gen/tmpl/fill.c +2 -2
  56. data/ext/cumo/narray/gen/tmpl/float_accum_kernel.cu +30 -0
  57. data/ext/cumo/narray/gen/tmpl/format.c +2 -2
  58. data/ext/cumo/narray/gen/tmpl/format_to_a.c +2 -2
  59. data/ext/cumo/narray/gen/tmpl/frexp.c +2 -2
  60. data/ext/cumo/narray/gen/tmpl/gemm.c +3 -3
  61. data/ext/cumo/narray/gen/tmpl/inspect.c +1 -1
  62. data/ext/cumo/narray/gen/tmpl/layer_norm.c +5 -5
  63. data/ext/cumo/narray/gen/tmpl/logseq.c +4 -4
  64. data/ext/cumo/narray/gen/tmpl/median.c +5 -5
  65. data/ext/cumo/narray/gen/tmpl/minmax.c +4 -4
  66. data/ext/cumo/narray/gen/tmpl/poly.c +2 -2
  67. data/ext/cumo/narray/gen/tmpl/pow.c +60 -15
  68. data/ext/cumo/narray/gen/tmpl/pow_kernel.cu +28 -16
  69. data/ext/cumo/narray/gen/tmpl/rand.c +7 -9
  70. data/ext/cumo/narray/gen/tmpl/rand_norm.c +6 -9
  71. data/ext/cumo/narray/gen/tmpl/rms_norm.c +4 -4
  72. data/ext/cumo/narray/gen/tmpl/seq.c +3 -3
  73. data/ext/cumo/narray/gen/tmpl/sort.c +4 -4
  74. data/ext/cumo/narray/gen/tmpl/sort_index.c +3 -3
  75. data/ext/cumo/narray/gen/tmpl/store.c +2 -2
  76. data/ext/cumo/narray/gen/tmpl/store_kernel.cu +1 -1
  77. data/ext/cumo/narray/gen/tmpl/to_a.c +1 -1
  78. data/ext/cumo/narray/gen/tmpl/unary.c +1 -1
  79. data/ext/cumo/narray/gen/tmpl/unary2.c +1 -1
  80. data/ext/cumo/narray/gen/tmpl/unary_ret2.c +1 -1
  81. data/ext/cumo/narray/gen/tmpl/unary_s.c +2 -2
  82. data/ext/cumo/narray/gen/tmpl_bit/aref.c +2 -2
  83. data/ext/cumo/narray/gen/tmpl_bit/aref_cpu.c +2 -2
  84. data/ext/cumo/narray/gen/tmpl_bit/aset.c +3 -3
  85. data/ext/cumo/narray/gen/tmpl_bit/binary.c +2 -2
  86. data/ext/cumo/narray/gen/tmpl_bit/bit_count.c +3 -3
  87. data/ext/cumo/narray/gen/tmpl_bit/bit_count_cpu.c +3 -3
  88. data/ext/cumo/narray/gen/tmpl_bit/bit_reduce.c +3 -3
  89. data/ext/cumo/narray/gen/tmpl_bit/bit_stat.c +3 -3
  90. data/ext/cumo/narray/gen/tmpl_bit/each.c +1 -1
  91. data/ext/cumo/narray/gen/tmpl_bit/each_with_index.c +1 -1
  92. data/ext/cumo/narray/gen/tmpl_bit/extract.c +1 -1
  93. data/ext/cumo/narray/gen/tmpl_bit/extract_cpu.c +1 -1
  94. data/ext/cumo/narray/gen/tmpl_bit/fill.c +2 -2
  95. data/ext/cumo/narray/gen/tmpl_bit/format.c +2 -2
  96. data/ext/cumo/narray/gen/tmpl_bit/format_to_a.c +2 -2
  97. data/ext/cumo/narray/gen/tmpl_bit/inspect.c +1 -1
  98. data/ext/cumo/narray/gen/tmpl_bit/mask.c +2 -2
  99. data/ext/cumo/narray/gen/tmpl_bit/to_a.c +1 -1
  100. data/ext/cumo/narray/gen/tmpl_bit/unary.c +1 -1
  101. data/ext/cumo/narray/gen/tmpl_bit/where.c +1 -1
  102. data/ext/cumo/narray/gen/tmpl_bit/where2.c +1 -1
  103. data/ext/cumo/narray/index.c +7 -4
  104. data/ext/cumo/narray/math.c +6 -6
  105. data/ext/cumo/narray/narray.c +54 -45
  106. data/ext/cumo/narray/ndloop.c +7 -7
  107. data/ext/cumo/narray/rand.c +7 -12
  108. data/ext/cumo/narray/struct.c +3 -3
  109. data/lib/cumo/cuda/compiler.rb +1 -1
  110. data/lib/cumo/cuda/elementwise_kernel.rb +7 -6
  111. data/lib/cumo/cuda/reduction_kernel.rb +7 -4
  112. data/lib/cumo/cuda/user_kernel.rb +5 -2
  113. data/lib/cumo/fft.rb +482 -0
  114. data/lib/cumo/linalg.rb +326 -79
  115. data/lib/cumo/narray/extra.rb +36 -5
  116. data/lib/cumo/pocketfft.rb +134 -0
  117. data/lib/cumo/version.rb +8 -0
  118. data/lib/cumo.rb +1 -0
  119. metadata +8 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48de893a0d01a5e8376c2a9cda402be28dd47835661f22bd69e7ed17561abed8
4
- data.tar.gz: c403ffc7d25b80a0b50e8513b81439ee46f775cbd8c126667f83656077488a79
3
+ metadata.gz: f09e6c09816ea16f1a27425e44170c7ea191b771f0a3b26b2b7298a1e53b6e6c
4
+ data.tar.gz: f4739b41e92d5f5920ea694393f3ab71241e9b428a29416db71e91d5fcb0f13e
5
5
  SHA512:
6
- metadata.gz: '00189a16920241771a70155a98d60c5d53b6fcd23bfcddbaed3094bbbe5d3476b2d6b5ce70f9d8d899e090a9bf3be2c4249c1b98b6d14360736a4a5ba1aa5594'
7
- data.tar.gz: bd6a1c8b78837b387f93f5b57f13c26917c71c123a1441deff8e3afb7250a42bc799ded9271b9ad9d46d4c3b928660c38653f85427b024bd00797d6786535734
6
+ metadata.gz: d58332736efabf28caeec996882ceb99435885e41d017a40911c6a85346a4f03428e32cb7cae32e8dae9633e47f7497dc35075309ec32d61d607504ef5f61fb1
7
+ data.tar.gz: d276cc4b4d19203ab34bfae56e96dd6e3220a780f07d3da8934e9c9241336fcb31c2040e013a7e461b47ec93461dcff21ca126597742c184e52221c0112bf196
data/CHANGELOG.md CHANGED
@@ -1,3 +1,51 @@
1
+ # 0.14.0 (2026/10/18)
2
+
3
+ Breaking changes:
4
+
5
+ * `cumo.h` no longer defines `CUMO_VERSION` or `CUMO_VERSION_CODE`. `Cumo::VERSION` and `Cumo::NArray::VERSION` are defined in Ruby, and the new `Cumo::REVISION` tells a release gem from one built from a checkout: it gives `"v0.14.0"` for this release and something like `"v0.14.0-3-g1a2b3c4"` for a gem built at a later commit (PR #617)
6
+
7
+ Fixes:
8
+
9
+ * Fix a host loop such as `each_with_index` yielding the old values of an array after a kernel launched from its block through `Function#launch`, `ElementwiseKernel` or `ReductionKernel` wrote the array (PR #625)
10
+
11
+ Changes:
12
+
13
+ * Add `Cumo::FFT`, the functions of `numpy.fft` on cuFFT with numpy's arguments as keywords: `fft`, `ifft`, `rfft`, `irfft`, `hfft`, `ihfft`, `fft2`, `ifft2`, `rfft2`, `irfft2`, `fftn`, `ifftn`, `rfftn`, `irfftn`, `fftfreq`, `rfftfreq`, `fftshift` and `ifftshift`, and `fftconvolve`, which answers the full linear convolution as `scipy.signal.fftconvolve` does. Load it with `require "cumo/fft"`. Single precision stays single, as in CuPy. cuFFT is loaded the first time a transform runs, and `Cumo::CUDA::Cufft.available?` says whether it can be. A thread keeps up to 16 plans for each device, which `Cumo::CUDA::Cufft.plan_cache_info` and `clear_plan_cache` show and let go (PR #623, PR #622, PR #621, PR #620)
14
+ * Add `Cumo::Pocketfft`, the functions of `Numo::Pocketfft` from numo-pocketfft on `Cumo::FFT`, with the same arguments, return types and errors. Load it with `require "cumo/pocketfft"`. A 0-dimensional array raises `Cumo::NArray::DimensionError`, where numo-pocketfft fails on it in a different way in each function (PR #623)
15
+ * Add `Cumo::NArray.from_numo` and `Cumo::NArray.to_numo`, which copy an array between Numo and Cumo and keep its type, where `Cumo::NArray.cast` read the elements one by one through a Ruby Array and turned an SFloat into a DFloat (closes issue #87, PR #619)
16
+ * `Cumo::Linalg.expm` of a dense matrix that needs squaring skips the check for a triangular matrix that 0.13.0 added, which makes a 64 x 64 expm about 8% faster in SFloat and 6% in DFloat (PR #618)
17
+
18
+ # 0.13.0 (2026/10/12)
19
+
20
+ Breaking changes:
21
+
22
+ * 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)
23
+
24
+ Fixes:
25
+
26
+ * 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)
27
+ * 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)
28
+ * 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)
29
+ * 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)
30
+ * 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)
31
+ * 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)
32
+ * 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)
33
+ * Fix `Cumo::Linalg.lu` and `matrix_power(a, 0)` raising `NoMethodError` for a Bit matrix. `P` and the identity are Bit matrices (PR #602)
34
+ * 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)
35
+
36
+ Changes:
37
+
38
+ * 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)
39
+ * `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)
40
+ * `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)
41
+ * `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)
42
+ * `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)
43
+ * 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)
44
+ * `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)
45
+ * `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)
46
+ * `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)
47
+ * 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)
48
+
1
49
  # 0.12.0 (2026/10/04)
2
50
 
3
51
  Fixes:
@@ -503,7 +551,7 @@ Fixes:
503
551
  * Remove unnecessary debug code
504
552
  * Fix capability list
505
553
  * Build only with supported capabilities to reduce compilation time
506
- * Fix SEGV when calling {mean, var, stddev, rms} on a single-element array (#154)
554
+ * Fix SEGV when calling `mean`, `var`, `stddev` or `rms` on a single-element array (#154)
507
555
  * Suppress warning message for deprecated declarations
508
556
  * Fix variable typo in complex log2 and log10 functions (#152)
509
557
 
@@ -545,7 +593,7 @@ Fixes:
545
593
  * Backport: fix macro: STORE_BIT STORE_BIT_STEP: requires mask to leave the lowest bit
546
594
  * Backport: fix NArray::Bit#any?,all?: empty array should return false
547
595
  * Backport: fix NArray::Bit#count_true/false: empty array should return zero
548
- * Backport: bug in NArray::Bit; fix bit operation in tmpl_bit/{store_bit,unary,binary}.c
596
+ * Backport: bug in NArray::Bit; fix bit operation in `tmpl_bit/{store_bit,unary,binary}.c`
549
597
  * Fix typo
550
598
  * Backport 135: Make all empty arrays equal
551
599
  * Backport: minor fixes in na_get_result_dimension(), check_index_count()
data/README.md CHANGED
@@ -97,6 +97,24 @@ end
97
97
  a = xm::DFloat.new(3,5).seq
98
98
  ```
99
99
 
100
+ ### Moving Arrays Between Numo And Cumo
101
+
102
+ `Cumo::NArray.from_numo` copies a Numo array to the GPU, and `Cumo::NArray.to_numo` copies a Cumo array back to the host.
103
+ The type keeps its name, so a `Numo::Int32` becomes a `Cumo::Int32` and back.
104
+ Both copy the bytes, where `Cumo::NArray.cast` would read the elements one by one through a Ruby Array.
105
+ A byte-swapped array arrives in host order.
106
+
107
+ ```ruby
108
+ require 'numo/narray'
109
+ require 'cumo/narray'
110
+
111
+ a = Cumo::NArray.from_numo(Numo::SFloat.new(3,5).seq) #=> Cumo::SFloat
112
+ b = Cumo::NArray.to_numo(a * 2) #=> Numo::SFloat
113
+ ```
114
+
115
+ Cumo does not depend on Numo. `to_numo` requires `numo/narray` when it is called, so Numo has to be installed for it.
116
+ `HFloat`, `BFloat` and `RObject` raise `TypeError`, since Numo has no half-precision types and an `RObject` holds Ruby objects rather than bytes.
117
+
100
118
  ### Incompatibility With Numo
101
119
 
102
120
  Numo returns a Ruby numeric object wherever a result is 0-dimensional, while Cumo returns the 0-dimensional NArray itself.
@@ -312,11 +330,44 @@ Cumo differs from numo-linalg-alt in a few places on purpose:
312
330
  * `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
331
  * `norm` takes the 2-norm and the nuclear norm of matrices stacked along other axes, which numo-linalg-alt refuses.
314
332
  * `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`.
333
+ * `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.
315
334
 
316
335
  A function waits for the current stream when it answers a Ruby number, or when it reads back whether cuSOLVER could do its work.
317
336
  `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.
318
337
  `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.
319
338
 
339
+ ### Fourier Transforms
340
+
341
+ `require "cumo/fft"` brings in `Cumo::FFT`, the functions of `numpy.fft` on the GPU through cuFFT.
342
+ They take numpy's arguments as keywords: `n:`, `axis:` and `norm:` for the one-dimensional transforms, and `s:`, `axes:` and `norm:` for the others.
343
+
344
+ ```ruby
345
+ require "cumo/fft"
346
+
347
+ x = Cumo::SFloat.new(1024).rand
348
+ spectrum = Cumo::FFT.rfft(x)
349
+ back = Cumo::FFT.irfft(spectrum, n: 1024)
350
+ image = Cumo::FFT.fft2(Cumo::DComplex.new(256, 256).rand, norm: "ortho")
351
+ freqs = Cumo::FFT.fftfreq(1024, d: 0.01)
352
+ ```
353
+
354
+ The functions are `fft`, `ifft`, `rfft`, `irfft`, `hfft`, `ihfft`, `fft2`, `ifft2`, `rfft2`, `irfft2`, `fftn`, `ifftn`, `rfftn`, `irfftn`, `fftfreq`, `rfftfreq`, `fftshift` and `ifftshift`, as in numpy.
355
+ `fftconvolve(a, b)` answers the full linear convolution, as `scipy.signal.fftconvolve` does in its `"full"` mode, and pads each axis to a length cuFFT computes fast on the way.
356
+ Single precision stays single: `SFloat`, `SComplex`, `HFloat` and `BFloat` give `SComplex`, or `SFloat` for a real result, and every other type gives `DComplex` or `DFloat`, as in numpy 2 and CuPy.
357
+ Errors follow numpy: an axis out of range raises `Cumo::NArray::DimensionError`, a transform of no points `ArgumentError`, and a complex input to `rfft` `TypeError`.
358
+
359
+ Cumo looks for the cuFFT header when it is built, and loads the library the first time a transform runs, so `require "cumo"` does not pay for it.
360
+ `Cumo::CUDA::Cufft.available?` answers whether it can be loaded.
361
+ `fftfreq`, `rfftfreq`, `fftshift` and `ifftshift` work without it.
362
+
363
+ A thread keeps up to 16 cuFFT plans for each device, and reuses one when a transform of the same type and shape comes again.
364
+ `Cumo::CUDA::Cufft.plan_cache_info` answers how many plans it holds and how often they were reused, and `Cumo::CUDA::Cufft.clear_plan_cache` lets them go.
365
+ A thread's plans also go when it ends, and when an allocation it makes from the memory pool runs out of memory.
366
+
367
+ `require "cumo/pocketfft"` brings in `Cumo::Pocketfft`, the functions of `Numo::Pocketfft` from [numo-pocketfft](https://github.com/yoshoku/numo-pocketfft).
368
+ They keep its single positional argument, its double precision results and the `ArgumentError`s it raises, so code written for numo-pocketfft runs on the GPU after the replacement of `Numo` with `Cumo` above.
369
+ A 0-dimensional array raises `Cumo::NArray::DimensionError`, where numo-pocketfft fails on it in a different way in each function.
370
+
320
371
  ### Fused Operations
321
372
 
322
373
  `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.
data/cumo.gemspec CHANGED
@@ -3,7 +3,8 @@
3
3
  lib = File.expand_path("../lib", __FILE__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
 
6
- cumo_version = File.read(File.join(__dir__, "ext/cumo/include/cumo.h")).match(/CUMO_VERSION "([^"]+)"/)[1]
6
+ # Requiring version.rb here would fix Cumo::REVISION to nil under bundle exec.
7
+ cumo_version = File.read(File.join(__dir__, "lib/cumo/version.rb"))[/VERSION = "([^"]+)"/, 1]
7
8
 
8
9
  Gem::Specification.new do |spec|
9
10
  spec.name = "cumo"
@@ -28,6 +29,14 @@ Gem::Specification.new do |spec|
28
29
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
29
30
  f.start_with?(*%w[.github/ test/ examples/ bin/ bench/ docker- Dockerfile Gemfile .gitignore .rubocop])
30
31
  end
32
+
33
+ revision = begin
34
+ IO.popen(%W[git -C #{__dir__} describe --tags --match v[0-9]* --dirty --always], err: File::NULL, &:read).strip
35
+ rescue SystemCallError
36
+ ""
37
+ end
38
+ spec.metadata["cumo_revision"] = revision unless revision.empty?
39
+
31
40
  spec.bindir = "exe"
32
41
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
42
  spec.require_paths = ["lib"]
@@ -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
  }