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.
- checksums.yaml +4 -4
- data/.yardopts +10 -0
- data/3rd_party/mkmf-cu/lib/mkmf-cu/cli.rb +1 -1
- data/CHANGELOG.md +50 -2
- data/README.md +37 -0
- data/cumo.gemspec +2 -2
- data/ext/cumo/cuda/cublas.c +12 -19
- data/ext/cumo/cuda/cudnn.c +10 -18
- data/ext/cumo/cuda/cudnn_impl.cpp +23 -30
- data/ext/cumo/cuda/cusolver.c +1407 -0
- data/ext/cumo/cuda/nvrtc.c +77 -11
- data/ext/cumo/cuda/runtime.c +137 -35
- data/ext/cumo/cumo.c +2 -0
- data/ext/cumo/depend.erb +8 -4
- data/ext/cumo/extconf.rb +37 -34
- data/ext/cumo/include/cumo/atomic.h +27 -0
- data/ext/cumo/include/cumo/cuda/cusolver.h +35 -0
- data/ext/cumo/include/cumo/cuda/runtime.h +37 -5
- data/ext/cumo/include/cumo/intern.h +2 -2
- data/ext/cumo/include/cumo/narray.h +2 -2
- data/ext/cumo/include/cumo/types/int_macro_kernel.h +1 -1
- data/ext/cumo/include/cumo/types/uint_macro_kernel.h +1 -2
- data/ext/cumo/include/cumo.h +2 -2
- data/ext/cumo/narray/array.c +7 -6
- data/ext/cumo/narray/data.c +54 -19
- data/ext/cumo/narray/data_kernel.cu +39 -0
- data/ext/cumo/narray/gen/cogen_kernel.rb +58 -1
- data/ext/cumo/narray/gen/cogen_runner.rb +16 -0
- data/ext/cumo/narray/gen/erbpp2.rb +7 -1
- data/ext/cumo/narray/gen/kernel_parts.rb +17 -0
- data/ext/cumo/narray/gen/narray_def.rb +2 -2
- data/ext/cumo/narray/gen/spec.rb +10 -0
- data/ext/cumo/narray/gen/tmpl/accum.c +9 -8
- data/ext/cumo/narray/gen/tmpl/accum_arg.c +3 -3
- data/ext/cumo/narray/gen/tmpl/accum_arg_kernel.cu +6 -2
- data/ext/cumo/narray/gen/tmpl/accum_binary.c +5 -5
- data/ext/cumo/narray/gen/tmpl/accum_binary_kernel.cu +2 -0
- data/ext/cumo/narray/gen/tmpl/accum_index.c +3 -3
- data/ext/cumo/narray/gen/tmpl/accum_index_kernel.cu +6 -2
- data/ext/cumo/narray/gen/tmpl/allocate.c +3 -1
- data/ext/cumo/narray/gen/tmpl/aref.c +2 -2
- data/ext/cumo/narray/gen/tmpl/aref_cpu.c +2 -2
- data/ext/cumo/narray/gen/tmpl/aset.c +3 -3
- data/ext/cumo/narray/gen/tmpl/at.c +2 -2
- data/ext/cumo/narray/gen/tmpl/binary.c +2 -2
- data/ext/cumo/narray/gen/tmpl/binary2.c +2 -2
- data/ext/cumo/narray/gen/tmpl/binary_s.c +3 -3
- data/ext/cumo/narray/gen/tmpl/bincount.c +7 -7
- data/ext/cumo/narray/gen/tmpl/cast.c +4 -2
- data/ext/cumo/narray/gen/tmpl/clip.c +3 -3
- data/ext/cumo/narray/gen/tmpl/coerce_cast.c +1 -1
- data/ext/cumo/narray/gen/tmpl/complex_accum_kernel.cu +51 -1
- data/ext/cumo/narray/gen/tmpl/cond_binary.c +2 -2
- data/ext/cumo/narray/gen/tmpl/cond_unary.c +1 -1
- data/ext/cumo/narray/gen/tmpl/cum.c +4 -7
- data/ext/cumo/narray/gen/tmpl/each.c +1 -1
- data/ext/cumo/narray/gen/tmpl/ewcomp.c +6 -5
- data/ext/cumo/narray/gen/tmpl/extract.c +1 -1
- data/ext/cumo/narray/gen/tmpl/extract_cpu.c +1 -1
- data/ext/cumo/narray/gen/tmpl/eye.c +5 -5
- data/ext/cumo/narray/gen/tmpl/fill.c +2 -2
- data/ext/cumo/narray/gen/tmpl/float_accum_kernel.cu +37 -1
- data/ext/cumo/narray/gen/tmpl/format.c +2 -2
- data/ext/cumo/narray/gen/tmpl/format_to_a.c +2 -2
- data/ext/cumo/narray/gen/tmpl/frexp.c +2 -2
- data/ext/cumo/narray/gen/tmpl/gemm.c +3 -3
- data/ext/cumo/narray/gen/tmpl/inspect.c +1 -1
- data/ext/cumo/narray/gen/tmpl/layer_norm.c +5 -5
- data/ext/cumo/narray/gen/tmpl/logseq.c +4 -4
- data/ext/cumo/narray/gen/tmpl/median.c +5 -5
- data/ext/cumo/narray/gen/tmpl/minmax.c +4 -4
- data/ext/cumo/narray/gen/tmpl/poly.c +2 -2
- data/ext/cumo/narray/gen/tmpl/pow.c +60 -15
- data/ext/cumo/narray/gen/tmpl/pow_kernel.cu +28 -16
- data/ext/cumo/narray/gen/tmpl/rand.c +7 -9
- data/ext/cumo/narray/gen/tmpl/rand_norm.c +6 -9
- data/ext/cumo/narray/gen/tmpl/real_accum_kernel.cu +3 -1
- data/ext/cumo/narray/gen/tmpl/rms_norm.c +4 -4
- data/ext/cumo/narray/gen/tmpl/seq.c +3 -3
- data/ext/cumo/narray/gen/tmpl/sort.c +4 -4
- data/ext/cumo/narray/gen/tmpl/sort_index.c +3 -3
- data/ext/cumo/narray/gen/tmpl/store.c +2 -2
- data/ext/cumo/narray/gen/tmpl/store_array.c +9 -6
- data/ext/cumo/narray/gen/tmpl/store_kernel.cu +1 -1
- data/ext/cumo/narray/gen/tmpl/to_a.c +1 -1
- data/ext/cumo/narray/gen/tmpl/unary.c +1 -1
- data/ext/cumo/narray/gen/tmpl/unary2.c +1 -1
- data/ext/cumo/narray/gen/tmpl/unary_ret2.c +1 -1
- data/ext/cumo/narray/gen/tmpl/unary_s.c +2 -2
- data/ext/cumo/narray/gen/tmpl_bit/aref.c +2 -2
- data/ext/cumo/narray/gen/tmpl_bit/aref_cpu.c +2 -2
- data/ext/cumo/narray/gen/tmpl_bit/aset.c +3 -3
- data/ext/cumo/narray/gen/tmpl_bit/binary.c +2 -2
- data/ext/cumo/narray/gen/tmpl_bit/bit_count.c +3 -3
- data/ext/cumo/narray/gen/tmpl_bit/bit_count_cpu.c +3 -3
- data/ext/cumo/narray/gen/tmpl_bit/bit_reduce.c +3 -3
- data/ext/cumo/narray/gen/tmpl_bit/bit_stat.c +3 -3
- data/ext/cumo/narray/gen/tmpl_bit/each.c +1 -1
- data/ext/cumo/narray/gen/tmpl_bit/each_with_index.c +1 -1
- data/ext/cumo/narray/gen/tmpl_bit/extract.c +1 -1
- data/ext/cumo/narray/gen/tmpl_bit/extract_cpu.c +1 -1
- data/ext/cumo/narray/gen/tmpl_bit/fill.c +2 -2
- data/ext/cumo/narray/gen/tmpl_bit/format.c +2 -2
- data/ext/cumo/narray/gen/tmpl_bit/format_to_a.c +2 -2
- data/ext/cumo/narray/gen/tmpl_bit/inspect.c +1 -1
- data/ext/cumo/narray/gen/tmpl_bit/mask.c +2 -2
- data/ext/cumo/narray/gen/tmpl_bit/store_array.c +3 -2
- data/ext/cumo/narray/gen/tmpl_bit/to_a.c +1 -1
- data/ext/cumo/narray/gen/tmpl_bit/unary.c +1 -1
- data/ext/cumo/narray/gen/tmpl_bit/where.c +1 -1
- data/ext/cumo/narray/gen/tmpl_bit/where2.c +1 -1
- data/ext/cumo/narray/index.c +7 -4
- data/ext/cumo/narray/math.c +6 -6
- data/ext/cumo/narray/narray.c +57 -45
- data/ext/cumo/narray/ndloop.c +7 -7
- data/ext/cumo/narray/rand.c +7 -12
- data/ext/cumo/narray/struct.c +3 -3
- data/lib/cumo/cuda/compiler.rb +1 -1
- data/lib/cumo/cuda/elementwise_kernel.rb +7 -6
- data/lib/cumo/cuda/reduction_kernel.rb +7 -4
- data/lib/cumo/cuda/user_kernel.rb +5 -2
- data/lib/cumo/linalg.rb +1639 -0
- data/lib/cumo/narray/extra.rb +13 -11
- data/lib/cumo.rb +2 -0
- metadata +9 -75
- data/.gitignore +0 -32
- data/.rubocop.yml +0 -15
- data/.rubocop_todo.yml +0 -1691
- data/Dockerfile +0 -34
- data/Gemfile +0 -14
- data/bench/bench.rb +0 -235
- data/bench/broadcast_fp32.rb +0 -140
- data/bench/cg_bench.rb +0 -271
- data/bench/crossover_bench.rb +0 -374
- data/bench/cumo_bench.rb +0 -218
- data/bench/cumo_probe.rb +0 -795
- data/bench/cumo_shape_probe.rb +0 -264
- data/bench/cumo_sync_probe.rb +0 -505
- data/bench/ising_bench.rb +0 -242
- data/bench/kmeans_bench.rb +0 -254
- data/bench/numo_bench.rb +0 -142
- data/bench/particle_bench.rb +0 -261
- data/bench/reduction_fp32.rb +0 -119
- data/bench/transformer_bench.rb +0 -264
- data/bin/console +0 -15
- data/bin/setup +0 -8
- data/docker-build.sh +0 -4
- data/docker-launch.sh +0 -4
- data/test/bfloat_test.rb +0 -238
- data/test/bit_test.rb +0 -1237
- data/test/cuda/compiler_test.rb +0 -153
- data/test/cuda/device_test.rb +0 -73
- data/test/cuda/driver_test.rb +0 -108
- data/test/cuda/elementwise_kernel_test.rb +0 -305
- data/test/cuda/function_test.rb +0 -162
- data/test/cuda/memory_pool_test.rb +0 -179
- data/test/cuda/module_test.rb +0 -15
- data/test/cuda/name_expression_test.rb +0 -172
- data/test/cuda/nvrtc_test.rb +0 -119
- data/test/cuda/pinned_memory_test.rb +0 -138
- data/test/cuda/reduction_kernel_test.rb +0 -276
- data/test/cuda/runtime_test.rb +0 -74
- data/test/cuda/stream_test.rb +0 -247
- data/test/cudnn_test.rb +0 -1008
- data/test/cumo_test.rb +0 -169
- data/test/extra_test.rb +0 -1209
- data/test/fused_test.rb +0 -608
- data/test/hfloat_test.rb +0 -717
- data/test/math_test.rb +0 -983
- data/test/narray_alt_coverage_test.rb +0 -980
- data/test/narray_test.rb +0 -8924
- data/test/ractor_test.rb +0 -54
- data/test/test_helper.rb +0 -105
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ad04bee56b44e50dfc5df236d806a034f1adef98415e6e78ffcdd73546047dfd
|
|
4
|
+
data.tar.gz: c202fc43967dc292dddeea61274b89be6095f8724a7cfb7ac37d299cbc56cc7b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 00b7285a170c241c968f2bba0ab4e80013f971c01964d8d092eefa4eb177ea06d68616326bf457b1e9a6a9c08ca8906b85456ba2ef91acbb13334292959b10a0
|
|
7
|
+
data.tar.gz: eba6aed6cdb916d1abee4d856a5496892ef5e33d3bb55cf4124fb01c3066a0324301baefe5d095798e21a0066af5d87c33ceca51142a00fec24936450b9f5946
|
data/.yardopts
ADDED
|
@@ -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
|
|
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.
|
|
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"]
|
data/ext/cumo/cuda/cublas.c
CHANGED
|
@@ -43,31 +43,22 @@ cumo_cuda_cublas_check_status(cublasStatus_t status)
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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(
|
|
58
|
+
cumo_cuda_cublas_check_status(cublasCreate(handle));
|
|
68
59
|
}
|
|
69
|
-
cumo_cuda_cublas_check_status(cublasSetStream(
|
|
70
|
-
return
|
|
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
|
*/
|
data/ext/cumo/cuda/cudnn.c
CHANGED
|
@@ -72,30 +72,21 @@ cumo_cuda_cudnn_release_conv_held(VALUE held)
|
|
|
72
72
|
return Qnil;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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(
|
|
86
|
+
cumo_cuda_cudnn_check_status(cudnnCreate(handle));
|
|
96
87
|
}
|
|
97
|
-
cumo_cuda_cudnn_check_status(cudnnSetStream(
|
|
98
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|