cumo 0.5.9 → 0.5.10

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/3rd_party/mkmf-cu/lib/mkmf-cu/cli.rb +3 -1
  3. data/CHANGELOG.md +40 -0
  4. data/Gemfile +1 -0
  5. data/README.md +66 -7
  6. data/ext/cumo/cuda/cudnn_impl.cpp +1 -1
  7. data/ext/cumo/cuda/memory_pool_impl.cpp +6 -1
  8. data/ext/cumo/cuda/memory_pool_impl_test.cpp +47 -0
  9. data/ext/cumo/extconf.rb +30 -1
  10. data/ext/cumo/include/cumo/cuda/cudnn.h +53 -0
  11. data/ext/cumo/include/cumo/indexer.h +21 -1
  12. data/ext/cumo/include/cumo/narray.h +4 -1
  13. data/ext/cumo/include/cumo/reduce_kernel.h +35 -6
  14. data/ext/cumo/include/cumo/types/complex_kernel.h +5 -3
  15. data/ext/cumo/include/cumo/types/float_macro_kernel.h +6 -4
  16. data/ext/cumo/include/cumo/types/int_macro_kernel.h +0 -4
  17. data/ext/cumo/include/cumo/types/robject_kernel.h +0 -3
  18. data/ext/cumo/include/cumo/types/uint_macro_kernel.h +1 -0
  19. data/ext/cumo/include/cumo.h +2 -2
  20. data/ext/cumo/narray/data.c +1 -1
  21. data/ext/cumo/narray/gen/spec.rb +1 -0
  22. data/ext/cumo/narray/gen/tmpl/accum.c +2 -11
  23. data/ext/cumo/narray/gen/tmpl/accum_arg.c +8 -14
  24. data/ext/cumo/narray/gen/tmpl/accum_arg_kernel.cu +104 -22
  25. data/ext/cumo/narray/gen/tmpl/accum_index.c +7 -26
  26. data/ext/cumo/narray/gen/tmpl/accum_index_kernel.cu +104 -22
  27. data/ext/cumo/narray/gen/tmpl/allocate.c +19 -0
  28. data/ext/cumo/narray/gen/tmpl/aset.c +8 -0
  29. data/ext/cumo/narray/gen/tmpl/batch_norm.c +8 -6
  30. data/ext/cumo/narray/gen/tmpl/batch_norm_backward.c +18 -8
  31. data/ext/cumo/narray/gen/tmpl/binary.c +76 -0
  32. data/ext/cumo/narray/gen/tmpl/binary_kernel.cu +57 -0
  33. data/ext/cumo/narray/gen/tmpl/binary_s.c +68 -9
  34. data/ext/cumo/narray/gen/tmpl/binary_s_kernel.cu +44 -7
  35. data/ext/cumo/narray/gen/tmpl/cast.c +6 -0
  36. data/ext/cumo/narray/gen/tmpl/clip.c +67 -0
  37. data/ext/cumo/narray/gen/tmpl/clip_kernel.cu +63 -17
  38. data/ext/cumo/narray/gen/tmpl/complex_accum_kernel.cu +145 -0
  39. data/ext/cumo/narray/gen/tmpl/cond_binary.c +53 -0
  40. data/ext/cumo/narray/gen/tmpl/cond_binary_kernel.cu +29 -6
  41. data/ext/cumo/narray/gen/tmpl/conv.c +7 -5
  42. data/ext/cumo/narray/gen/tmpl/conv_grad_w.c +13 -10
  43. data/ext/cumo/narray/gen/tmpl/conv_transpose.c +8 -7
  44. data/ext/cumo/narray/gen/tmpl/fixed_batch_norm.c +8 -6
  45. data/ext/cumo/narray/gen/tmpl/float_accum_kernel.cu +120 -0
  46. data/ext/cumo/narray/gen/tmpl/gemm.c +82 -8
  47. data/ext/cumo/narray/gen/tmpl/minmax.c +6 -7
  48. data/ext/cumo/narray/gen/tmpl/new_dim0.c +17 -0
  49. data/ext/cumo/narray/gen/tmpl/pooling_backward.c +18 -3
  50. data/ext/cumo/narray/gen/tmpl/pooling_forward.c +7 -5
  51. data/ext/cumo/narray/gen/tmpl/pow.c +91 -0
  52. data/ext/cumo/narray/gen/tmpl/pow_kernel.cu +60 -12
  53. data/ext/cumo/narray/gen/tmpl/real_accum_kernel.cu +87 -0
  54. data/ext/cumo/narray/gen/tmpl/seq_kernel.cu +13 -1
  55. data/ext/cumo/narray/gen/tmpl/sort_index.c +5 -2
  56. data/ext/cumo/narray/gen/tmpl_bit/aset.c +8 -0
  57. data/ext/cumo/narray/gen/tmpl_bit/mask.c +20 -5
  58. data/ext/cumo/narray/gen/tmpl_bit/mask_kernel.cu +54 -0
  59. data/ext/cumo/narray/index.c +19 -3
  60. data/ext/cumo/narray/index_kernel.cu +4 -4
  61. data/ext/cumo/narray/narray.c +16 -4
  62. data/ext/cumo/narray/ndloop.c +0 -1
  63. data/test/bit_test.rb +20 -4
  64. data/test/cudnn_test.rb +134 -0
  65. data/test/cumo_test.rb +59 -0
  66. data/test/narray_test.rb +1066 -0
  67. metadata +2 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f211eb1858b22b8418a8494efb431afeca60f023de409d74e6bdb1ae1fc3753a
4
- data.tar.gz: '085be7ea42f37e235945e62783c51522c206c22eb539557b965812cc71ce422d'
3
+ metadata.gz: bcb51638cb488e5abcb0bf885744ea1478f4daefd4945f6af8becf252bc85f29
4
+ data.tar.gz: 75aed5ea382f37a823992e29ef2f3fe9ee31a7b7c11350d2541e5f4fc0c0fb59
5
5
  SHA512:
6
- metadata.gz: 19a6ca3c65b190e2347624232fa33302f4485e84d90df7de6972d727d76353e20a15a00bf8a7c8c96d281a97bbd7f8084016b8c2961e6781e4d94b00b17287d1
7
- data.tar.gz: 2df512d0a5e2b201cd82cc91d4dc258f4d3463097e2969250ab0aa81ba591abaaa89ea0c6d1e818d8b85f32d698a6b9cc529d961a8e432be540fb7f4144f2e70
6
+ metadata.gz: ee03167a0966008c99598d90623757772060c09bad859af35d313dda831975461cba49c8da251a4793e6a9b933cd5ec75588038381d55e492398d0b4e2686559
7
+ data.tar.gz: 2e2b3e1a6f0f304be8b2375be48061ba73926973d4ec966e49d0b50f666d88d7078cec3c59ea797401bd72b91ec96a4f115bf2ed49485dc1d7ca3b6a29fb1c48
@@ -40,7 +40,9 @@ module MakeMakefileCuda
40
40
  s = MakeMakefileCuda::Nvcc.generate(argv)
41
41
  cmd = "nvcc #{s}"
42
42
  if ENV['CUMO_NVCC_GENERATE_CODE']
43
- cmd << " --generate-code=#{ENV['CUMO_NVCC_GENERATE_CODE']}"
43
+ ENV['CUMO_NVCC_GENERATE_CODE'].split.each do |code|
44
+ cmd << " --generate-code=#{code}"
45
+ end
44
46
  else
45
47
  capability = nil
46
48
  if find_executable('nvidia-smi')
data/CHANGELOG.md CHANGED
@@ -1,3 +1,43 @@
1
+ # 0.5.10 (2026/08/23)
2
+
3
+ Breaking changes:
4
+
5
+ * An indexed assignment to a frozen view raises instead of writing through it; `store` and `fill` already raised (PR #306)
6
+ * The cuDNN and cuBLAS entry points turn away arguments they used to read or write out of bounds: `pooling_backward` requires `y` and `gy` to carry its own dtype and the pooling output shape of `x`, `gemm` requires a contiguous `c`, `batch_norm` requires the reduced size to cover x's channels, and `conv_grad_w` raises on a mismatched `gy` rather than asserting (PR #303, PR #302, PR #300, PR #278)
7
+ * `seq` on the unsigned types wraps a negative start and a start past 2**63 the way `fill` and `cast` do, where they collapsed to zero and clamped to INT64_MAX (PR #295, PR #294)
8
+ * A negative power of an unsigned array answers zero instead of spinning the device until SIGKILL (PR #293)
9
+ * A subscript that is a Cumo::RObject subclass, and an `expand_dims` past `CUMO_NA_MAX_DIMENSION`, raise instead of being taken (PR #272, PR #270)
10
+
11
+ Fixes:
12
+
13
+ * Fix `[]=` writing through a frozen view, the store going to a derived view whose data object is the root rather than the receiver (PR #306)
14
+ * Fix `at()` reading the accumulator in place of the subscript on a reversed view, which addressed far in front of the buffer (PR #305)
15
+ * Fix `cumo_cuda_cudnn_CreateBNTensorDescriptor` returning before it derived the descriptor, an inverted status check that no caller reached (PR #304)
16
+ * Fix `batch_norm`, `fixed_batch_norm` and `batch_norm_backward` reading and writing past their parameters, the sizes being checked against the reduced shape while cuDNN reaches x's channel count (PR #303)
17
+ * Fix `gemm` writing past its allocation when `c` is a non-contiguous inplace view, and over whatever the pool put next to it (PR #302)
18
+ * Fix `pooling_backward` reading past `y` and `gy`, and returning what it read in `gx` (PR #300)
19
+ * Fix `pow_int` negating INT32_MIN, which overflows and leaves the loop unbounded (PR #299)
20
+ * Fix the device free running for a subscript that allocated no index array (PR #297)
21
+ * Fix a NULL dereference from `at()` given a scalar subscript (PR #271)
22
+ * Fix an out-of-bounds write through an unchecked cuDNN output array (PR #269)
23
+ * Fix `gemm` reading past an operand that carries fewer batch dimensions (PR #268)
24
+
25
+ Changes:
26
+
27
+ * Pass a numeric operand to the kernel instead of casting it to a 0-dimensional array first, in the operators, `clip`, `pow`, the comparisons, the NMath functions and the coerced left-hand side; GPT-2 124M decode drops from 787 to 677 kernel launches a token (PR #296, PR #291, PR #290, PR #289, PR #288, PR #287)
28
+ * Run the NMath binary functions through the indexer loop instead of one kernel launch per row; 11x on a transposed operand and up to 60x on the cheap kernels (PR #292)
29
+ * Copy a reduction operand only when it carries an index array, where any non-contiguous view was copied whole (PR #286)
30
+ * Run the nan-aware reductions, the nan-aware index reductions, `kahan_sum` and `Bit#mask` on the GPU (PR #277, PR #276, PR #275, PR #273)
31
+ * Take a transposed batch of matrices with the cuBLAS transpose flag rather than duplicating it, which saves a temporary the size of the operand (PR #283)
32
+ * Emit indexer accessors up to eight dimensions (PR #284)
33
+ * Drop a free-list bin from the pool arena when `Malloc` empties it (PR #279)
34
+ * Check the cuDNN header and version, not just the library (PR #280)
35
+ * Document every method that returns a 0-dimensional NArray, and what keeping scalars on the device buys (PR #298, PR #282)
36
+ * Build two GPU architectures in CI instead of every one (PR #285)
37
+ * Add `fiddle` to the Gemfile (PR #281)
38
+ * Remove TODO comments whose questions have been answered (PR #274)
39
+ * Cover the Int32 array exponent in the INT_MIN power test (PR #301)
40
+
1
41
  # 0.5.9 (2026/08/20)
2
42
 
3
43
  Breaking changes:
data/Gemfile CHANGED
@@ -5,6 +5,7 @@ source "https://rubygems.org"
5
5
  gemspec
6
6
 
7
7
  gem 'extconf_compile_commands_json'
8
+ gem 'fiddle'
8
9
  gem 'rake-compiler'
9
10
  gem 'test-unit'
10
11
  gem 'yard'
data/README.md CHANGED
@@ -9,6 +9,7 @@ Cumo (pronounced "koomo") is a CUDA-aware, GPU-optimized numerical library that
9
9
  * Ruby 3.0 or later
10
10
  * NVIDIA GPU Compute Capability 3.5 (Kepler) or later
11
11
  * CUDA 11.0 or later
12
+ * cuDNN 8.0 or later (optional, for the cuDNN features)
12
13
 
13
14
  ## Preparation
14
15
 
@@ -97,17 +98,31 @@ a = xm::DFloat.new(3,5).seq
97
98
 
98
99
  ### Incompatibility With Numo
99
100
 
100
- The following methods behave incompatibly with Numo by default for performance reasons:
101
+ Numo returns a Ruby numeric object wherever a result is 0-dimensional, while Cumo returns the 0-dimensional NArray itself.
102
+ Cumo differs in this way to avoid synchronization and minimize CPU ⇄ GPU data transfer.
103
+ That is not only a cost of the port; see [Keeping Scalars On The Device](#keeping-scalars-on-the-device) for what it buys.
101
104
 
102
- * `extract`
103
- * `[]`
104
- * `count_true`
105
- * `count_false`
105
+ The methods affected are:
106
106
 
107
- Numo returns a Ruby numeric object for 0-dimensional NArray, while Cumo returns the 0-dimensional NArray instead of a Ruby numeric object.
108
- Cumo differs in this way to avoid synchronization and minimize CPU ⇄ GPU data transfer.
107
+ * `[]` and `extract`
108
+ * `count_true` and `count_false`
109
+ * reductions down to a single value: `sum`, `prod`, `mean`, `stddev`, `var`, `rms`, `min`, `max`, `ptp`, `minmax`, `median`, `mulsum`, `dot`, `inner`
110
+ * index reductions: `max_index`, `min_index`, `argmax`, `argmin`
111
+
112
+ A 0-dimensional `Cumo::Bit` is truthy even when it holds 0, because Ruby treats every object but `nil` and `false` as true.
113
+ Comparing two scalars therefore takes the wrong branch without raising anything:
114
+
115
+ ```ruby
116
+ a = Cumo::SFloat[5.0]
117
+ a[0] < 1.0 #=> Cumo::Bit#shape=[] holding 0
118
+ (a[0] < 1.0) ? :yes : :no #=> :yes, where Numo gives :no
119
+ ```
120
+
121
+ `assert_operator(a[0], :<, 1.0)` passes for the same reason, so a test suite written for Numo can stay green against Cumo while asserting nothing.
122
+ Read the value back to the host before branching on it, or run under `compatible_mode`.
109
123
 
110
124
  Set the `CUMO_COMPATIBLE_MODE` environment variable to `ON` to force Numo NArray compatibility (for worse performance).
125
+ Running a Numo test suite that way keeps its assertions meaningful.
111
126
 
112
127
  You may enable or disable `compatible_mode` as:
113
128
 
@@ -126,6 +141,43 @@ You can also use the following methods which behave like Numo's NArray methods.
126
141
  * `count_true_cpu`
127
142
  * `count_false_cpu`
128
143
 
144
+ ```ruby
145
+ a.aref_cpu(0) < 1.0 #=> false in either mode
146
+ Float(a.sum) #=> 7.0 in either mode
147
+ ```
148
+
149
+ They are methods on an NArray, so chaining one onto a result that `compatible_mode` has already turned into a Ruby object, as in `a.sum.extract_cpu`, raises `NoMethodError` while the mode is on.
150
+ `Kernel#Float` and `Kernel#Integer` read either representation, and read Numo's too, so they are what code that runs against both libraries wants.
151
+
152
+ ### Keeping Scalars On The Device
153
+
154
+ The 0-dimensional return is what lets an iterative loop stay on the GPU.
155
+ Reading a scalar back to the host waits for everything queued behind it, so every read caps how far ahead the GPU is allowed to run.
156
+ What a read costs is not a fixed price either: it is however much work happens to be queued when it is taken.
157
+
158
+ `bench/cg_bench.rb` prices this with a conjugate gradient solve, 200 iterations over a 512x512 grid on an RTX 5070 Ti Laptop:
159
+
160
+ ```
161
+ scalars convergence test us/iter readbacks/iter
162
+ Ruby Floats every iteration 136.7 2.02
163
+ Ruby Floats never 134.4 2.02
164
+ 0-dim NArray every iteration 142.6 1.02
165
+ 0-dim NArray every 20th 65.6 0.06
166
+ 0-dim NArray never 60.3 0.02
167
+ ```
168
+
169
+ Written with Ruby Floats the loop reads back twice an iteration whatever the convergence test does, since `alpha` needs `pap` and `beta` needs `rs_new` as Floats.
170
+ Thinning the test cannot get under that floor, and keeping the scalars as 0-dimensional NArrays buys nothing on its own.
171
+ The two only pay together, and together they are worth 2.1x.
172
+ The relative residual is identical in every row.
173
+
174
+ ```ruby
175
+ alpha = rs_old / pap # a 0-dimensional NArray, divided on the device
176
+ x += p_dir * alpha # and consumed there, without crossing the bus
177
+ ```
178
+
179
+ Read the value back once the loop is done, or every k iterations if it has to test something.
180
+
129
181
  ### Select a GPU device ID
130
182
 
131
183
  Set the `CUDA_VISIBLE_DEVICES=id` environment variable, or
@@ -201,7 +253,14 @@ ln -sf "$HOME/opt/ccache/bin/ccache" "$HOME/opt/ccache/bin/nvcc"
201
253
  bundle exec env CUMO_NVCC_GENERATE_CODE=arch=compute_60,code=sm_60 rake compile
202
254
  ```
203
255
 
256
+ Separate the entries with a space to build for more than one architecture:
257
+
258
+ ```
259
+ bundle exec env CUMO_NVCC_GENERATE_CODE="arch=compute_75,code=sm_75 arch=compute_121,code=sm_121" rake compile
260
+ ```
261
+
204
262
  This is useful even on development because it makes it possible to skip JIT compilation of PTX to cubin during runtime.
263
+ Without it, and without an `nvidia-smi` to read the local compute capability from, the build covers every architecture the CUDA version supports.
205
264
 
206
265
  ### Run tests with gdb
207
266
 
@@ -555,7 +555,7 @@ cumo_cuda_cudnn_CreateBNTensorDescriptor(
555
555
  {
556
556
  cudnnStatus_t status = CUDNN_STATUS_SUCCESS;
557
557
  status = cudnnCreateTensorDescriptor(desc);
558
- if (status == CUDNN_STATUS_SUCCESS) return status;
558
+ if (status != CUDNN_STATUS_SUCCESS) return status;
559
559
 
560
560
  status = cudnnDeriveBNTensorDescriptor(*desc, x_desc, mode);
561
561
  return status;
@@ -140,7 +140,12 @@ intptr_t SingleDeviceMemoryPool::Malloc(size_t size, cudaStream_t stream_ptr) {
140
140
  continue;
141
141
  }
142
142
  chunk = PopFromFreeList(free_list);
143
- // TODO(sonots): compact_index
143
+ if (free_list.empty()) {
144
+ // An emptied bin stays in the arena otherwise, and every later
145
+ // search walks it. Dropping it here invalidates arena and
146
+ // free_list, so nothing below this loop may touch them.
147
+ CompactIndex(stream_ptr, false);
148
+ }
144
149
  break;
145
150
  }
146
151
 
@@ -137,6 +137,8 @@ public:
137
137
 
138
138
  void Run() {
139
139
  TearDown(); SetUp(); TestGetRoundedSize();
140
+ TearDown(); SetUp(); TestMallocCompactsEmptiedBin();
141
+ TearDown(); SetUp(); TestMallocCompactsLastBin();
140
142
  TearDown(); SetUp(); TestGetBinIndex();
141
143
  TearDown(); SetUp(); TestGetArenaIndexWithHugeSize();
142
144
  TearDown(); SetUp(); TestMallocOnAnotherStream();
@@ -290,6 +292,51 @@ public:
290
292
  assert(arena_index_map[2] == 4);
291
293
  }
292
294
 
295
+ // Malloc drops a bin it just emptied, so a later search does not walk it.
296
+ void TestMallocCompactsEmptiedBin() {
297
+ Arena& arena = pool_->GetArena(stream_ptr_);
298
+ ArenaIndexMap& arena_index_map = pool_->GetArenaIndexMap(stream_ptr_);
299
+
300
+ // three bins, one chunk each, none of them adjacent in memory
301
+ for (int k = 2; k <= 4; ++k) {
302
+ auto mem = std::make_shared<Memory>(kRoundSize * k);
303
+ auto chunk = std::make_shared<Chunk>(mem, 0, mem->size(), stream_ptr_);
304
+ pool_->AppendToFreeList(chunk->size(), chunk, stream_ptr_);
305
+ }
306
+ assert(arena.size() == 3);
307
+ assert(arena_index_map.size() == 3);
308
+
309
+ // takes the whole chunk of the middle bin, which then has nothing left
310
+ intptr_t ptr = pool_->Malloc(kRoundSize * 3, stream_ptr_);
311
+ assert(ptr != 0);
312
+ assert(arena.size() == 2);
313
+ assert(arena_index_map.size() == 2);
314
+ assert(arena_index_map[0] == 1);
315
+ assert(arena_index_map[1] == 3);
316
+
317
+ pool_->Free(ptr);
318
+ assert(arena.size() == 3);
319
+ }
320
+
321
+ // Emptying the last bin leaves no arena at all, and the pool has to carry
322
+ // on: the split remainder of that very allocation goes back into a new one.
323
+ void TestMallocCompactsLastBin() {
324
+ auto mem = std::make_shared<Memory>(kRoundSize * 8);
325
+ auto chunk = std::make_shared<Chunk>(mem, 0, mem->size(), stream_ptr_);
326
+ pool_->AppendToFreeList(chunk->size(), chunk, stream_ptr_);
327
+ assert(pool_->GetArena(stream_ptr_).size() == 1);
328
+
329
+ intptr_t ptr = pool_->Malloc(kRoundSize * 2, stream_ptr_);
330
+ assert(ptr != 0);
331
+ // the remaining 6 units are back in a free list
332
+ assert(pool_->GetNumFreeBlocks() == 1);
333
+ assert(pool_->GetFreeBytes() == kRoundSize * 6);
334
+
335
+ pool_->Free(ptr);
336
+ assert(pool_->GetNumFreeBlocks() == 1);
337
+ assert(pool_->GetFreeBytes() == kRoundSize * 8);
338
+ }
339
+
293
340
  // TODO(sonots): Fix after implementing compaction
294
341
  void TestRemoveFromFreeList() {
295
342
  Arena& arena = pool_->GetArena(stream_ptr_);
data/ext/cumo/extconf.rb CHANGED
@@ -164,7 +164,36 @@ have_library('nvrtc')
164
164
  have_library('cublas')
165
165
  # have_library('cusolver')
166
166
  # have_library('curand')
167
- if have_library('cudnn') # TODO(sonots): cuDNN version check
167
+
168
+ # cuDNN 8 is the first release that supports CUDA 11, which cumo requires.
169
+ CUDNN_MIN_MAJOR = 8
170
+
171
+ def have_cudnn?
172
+ unless have_header('cudnn.h')
173
+ message("cuDNN header not found; building without cuDNN features\n")
174
+ return false
175
+ end
176
+ ok = checking_for("cuDNN #{CUDNN_MIN_MAJOR} or later") do
177
+ try_compile(<<-SRC)
178
+ #include <cudnn.h>
179
+ #if CUDNN_MAJOR < #{CUDNN_MIN_MAJOR}
180
+ #error "cuDNN is too old"
181
+ #endif
182
+ int main(void) { return 0; }
183
+ SRC
184
+ end
185
+ unless ok
186
+ message("cuDNN is older than #{CUDNN_MIN_MAJOR}.0; building without cuDNN features\n")
187
+ return false
188
+ end
189
+ unless have_library('cudnn')
190
+ message("cuDNN library not found; building without cuDNN features\n")
191
+ return false
192
+ end
193
+ true
194
+ end
195
+
196
+ if have_cudnn?
168
197
  $CFLAGS << " -DCUDNN_FOUND"
169
198
  $CXXFLAGS << " -DCUDNN_FOUND"
170
199
  end
@@ -44,6 +44,59 @@ extern VALUE cumo_na_eShapeError;
44
44
  (int)(nd1), (int)(nd2)); \
45
45
  }
46
46
 
47
+ // An output array given by the caller is written through a descriptor built
48
+ // from another operand, or as if it were contiguous, so cuDNN never learns how
49
+ // long it really is. It has to be checked here instead.
50
+ static inline void
51
+ cumo_cuda_cudnn_check_output(VALUE out, VALUE type, size_t ndim, size_t *shape)
52
+ {
53
+ cumo_narray_t *na;
54
+
55
+ CUMO_CUDA_CUDNN_CHECK_NARRAY_TYPE(out, type);
56
+ CumoGetNArray(out, na);
57
+ CUMO_CUDA_CUDNN_CHECK_DIM_EQ((size_t)(na->ndim), ndim);
58
+ for (size_t idim = 0; idim < ndim; ++idim) {
59
+ CUMO_CUDA_CUDNN_CHECK_SIZE_EQ(na->shape[idim], shape[idim]);
60
+ }
61
+ if (cumo_na_check_contiguous(out) != Qtrue) {
62
+ rb_raise(cumo_na_eShapeError, "output NArray must be contiguous");
63
+ }
64
+ }
65
+
66
+ // cuDNN derives the batch-norm parameter descriptor from x with a SPATIAL mode,
67
+ // so it reaches x's channel count in gamma, beta and the rest however the axis
68
+ // reduced them. Normally the reduced size is the larger of the two and only
69
+ // part of each buffer is used, but an axis that folds the channel away makes it
70
+ // smaller, and cuDNN has no way to know.
71
+ static inline void
72
+ cumo_cuda_cudnn_check_reduced_size(size_t reduced_total_size, size_t x_ndim, size_t *x_shape)
73
+ {
74
+ size_t channel_size = (x_ndim == 1) ? x_shape[0] : x_shape[1];
75
+
76
+ if (reduced_total_size < channel_size) {
77
+ rb_raise(cumo_na_eShapeError,
78
+ "the axis reduces to %d, fewer than the %d channels cuDNN accesses",
79
+ (int)reduced_total_size, (int)channel_size);
80
+ }
81
+ }
82
+
83
+ // An input array is read through a descriptor built from another operand, so
84
+ // cuDNN reads whatever length that descriptor claims rather than the length the
85
+ // array really has. A non-contiguous one is copied before it is read, so unlike
86
+ // an output it does not have to be contiguous here.
87
+ static inline void
88
+ cumo_cuda_cudnn_check_input(VALUE in, VALUE type, size_t ndim, size_t *shape)
89
+ {
90
+ cumo_narray_t *na;
91
+
92
+ CUMO_CUDA_CUDNN_CHECK_NARRAY_TYPE(in, type);
93
+ CumoGetNArray(in, na);
94
+ CUMO_CUDA_CUDNN_CHECK_DIM_EQ((size_t)(na->ndim), ndim);
95
+ for (size_t idim = 0; idim < ndim; ++idim) {
96
+ CUMO_CUDA_CUDNN_CHECK_SIZE_EQ(na->shape[idim], shape[idim]);
97
+ }
98
+ }
99
+
47
100
  void
48
101
  cumo_cuda_cudnn_check_status(cudnnStatus_t status);
49
102
 
@@ -221,7 +221,7 @@ cumo_na_make_reduction_arg(cumo_na_loop_t* lp_user, int out_arg)
221
221
 
222
222
  #endif // #ifndef __CUDACC__
223
223
 
224
- #define CUMO_NA_INDEXER_OPTIMIZED_NDIM 4
224
+ #define CUMO_NA_INDEXER_OPTIMIZED_NDIM 8
225
225
 
226
226
  #ifdef __CUDACC__
227
227
 
@@ -247,6 +247,10 @@ cumo_na_indexer_set_dim##NDIM(cumo_na_indexer_t* indexer, uint64_t i) { \
247
247
  } \
248
248
  }
249
249
 
250
+ CUMO_NA_INDEXER_SET(8)
251
+ CUMO_NA_INDEXER_SET(7)
252
+ CUMO_NA_INDEXER_SET(6)
253
+ CUMO_NA_INDEXER_SET(5)
250
254
  CUMO_NA_INDEXER_SET(4)
251
255
  CUMO_NA_INDEXER_SET(3)
252
256
  CUMO_NA_INDEXER_SET(2)
@@ -280,6 +284,10 @@ cumo_na_iarray_at_dim##NDIM(cumo_na_iarray_t* iarray, cumo_na_indexer_t* indexer
280
284
  return ptr; \
281
285
  }
282
286
 
287
+ CUMO_NA_IARRAY_AT(8)
288
+ CUMO_NA_IARRAY_AT(7)
289
+ CUMO_NA_IARRAY_AT(6)
290
+ CUMO_NA_IARRAY_AT(5)
283
291
  CUMO_NA_IARRAY_AT(4)
284
292
  CUMO_NA_IARRAY_AT(3)
285
293
  CUMO_NA_IARRAY_AT(2)
@@ -313,6 +321,10 @@ cumo_na_bit_iarray_at_dim##NDIM(cumo_na_bit_iarray_t* iarray, cumo_na_indexer_t*
313
321
  return pos; \
314
322
  }
315
323
 
324
+ CUMO_NA_BIT_IARRAY_AT(8)
325
+ CUMO_NA_BIT_IARRAY_AT(7)
326
+ CUMO_NA_BIT_IARRAY_AT(6)
327
+ CUMO_NA_BIT_IARRAY_AT(5)
316
328
  CUMO_NA_BIT_IARRAY_AT(4)
317
329
  CUMO_NA_BIT_IARRAY_AT(3)
318
330
  CUMO_NA_BIT_IARRAY_AT(2)
@@ -354,6 +366,10 @@ cumo_na_bit_iarray_stridx_at_dim##NDIM(cumo_na_bit_iarray_stridx_t* iarray, cumo
354
366
  return pos; \
355
367
  }
356
368
 
369
+ CUMO_NA_BIT_IARRAY_STRIDX_AT(8)
370
+ CUMO_NA_BIT_IARRAY_STRIDX_AT(7)
371
+ CUMO_NA_BIT_IARRAY_STRIDX_AT(6)
372
+ CUMO_NA_BIT_IARRAY_STRIDX_AT(5)
357
373
  CUMO_NA_BIT_IARRAY_STRIDX_AT(4)
358
374
  CUMO_NA_BIT_IARRAY_STRIDX_AT(3)
359
375
  CUMO_NA_BIT_IARRAY_STRIDX_AT(2)
@@ -401,6 +417,10 @@ cumo_na_iarray_stridx_at_dim##NDIM(cumo_na_iarray_stridx_t* iarray, cumo_na_inde
401
417
  return ptr; \
402
418
  }
403
419
 
420
+ CUMO_NA_IARRAY_STRIDX_AT(8)
421
+ CUMO_NA_IARRAY_STRIDX_AT(7)
422
+ CUMO_NA_IARRAY_STRIDX_AT(6)
423
+ CUMO_NA_IARRAY_STRIDX_AT(5)
404
424
  CUMO_NA_IARRAY_STRIDX_AT(4)
405
425
  CUMO_NA_IARRAY_STRIDX_AT(3)
406
426
  CUMO_NA_IARRAY_STRIDX_AT(2)
@@ -471,7 +471,10 @@ cumo_na_has_idx_p(VALUE obj)
471
471
  if (CUMO_NA_TYPE(na) == CUMO_NARRAY_VIEW_T) {
472
472
  CumoGetNArrayView(obj, nv);
473
473
  for (; i < nv->base.ndim; ++i) {
474
- if (nv->stridx[i].index) {
474
+ // A stride is tagged with its low bit set, so the raw union is
475
+ // non-null for every dimension a view has and reading it as a
476
+ // pointer answers true for all of them.
477
+ if (CUMO_SDX_IS_INDEX(nv->stridx[i])) {
475
478
  return true;
476
479
  }
477
480
  }
@@ -350,7 +350,7 @@ __global__ static void reduction_pair_kernel(cumo_na_reduction_arg_t arg, cumo_n
350
350
  // The scratch is laid out by output so the combine pass can read it flat, but
351
351
  // threads are handed out by output first, so a block still covers consecutive
352
352
  // outputs when those are the ones running along memory.
353
- template <bool FLAT, typename TypeIn, typename TypeReduce, typename ReductionImpl>
353
+ template <bool FLAT, bool ARG, typename TypeIn, typename TypeReduce, typename ReductionImpl>
354
354
  __global__ static void reduction_partial_kernel(cumo_na_reduction_arg_t arg, cumo_reduce_addr_t ad, TypeReduce* partial, int64_t n_split, int64_t chunk, int out_block_size, int reduce_block_size, ReductionImpl impl) {
355
355
  extern __shared__ __align__(8) char sdata_raw[];
356
356
  TypeReduce* sdata = reinterpret_cast<TypeReduce*>(sdata_raw);
@@ -374,7 +374,7 @@ __global__ static void reduction_partial_kernel(cumo_na_reduction_arg_t arg, cum
374
374
  ssize_t in_out_off = reduce_in_out_offset<FLAT>(arg, ad, i_out);
375
375
  int64_t i_in = i_out * reduce_total_size + begin + reduce_offset;
376
376
 
377
- TypeReduce accum = reduce_axis<FLAT,false,TypeIn>(arg, ad, impl, in_out_off, i_in, begin, end, reduce_offset, reduce_block_size);
377
+ TypeReduce accum = reduce_axis<FLAT,ARG,TypeIn>(arg, ad, impl, in_out_off, i_in, begin, end, reduce_offset, reduce_block_size);
378
378
 
379
379
  accum = reduce_in_block(accum, sdata, tid, out_block_size, impl);
380
380
  if (reduce_offset == 0) {
@@ -410,7 +410,7 @@ struct reduce_combine {
410
410
  // Allocates the scratch and runs the first pass, then points arg2's input at
411
411
  // the partials so the caller can combine them with its own second pass. The
412
412
  // caller frees the returned buffer.
413
- template <typename TypeIn, typename TypeReduce, typename ReductionImpl>
413
+ template <typename TypeIn, typename TypeReduce, typename ReductionImpl, bool ARG = false>
414
414
  TypeReduce* reduce_partial_pass(cumo_na_reduction_arg_t arg, cumo_reduce_addr_t ad, int64_t n_split, int64_t reduce_total_size, cumo_na_reduction_arg_t* arg2, ReductionImpl& impl) {
415
415
  int64_t chunk = (reduce_total_size + n_split - 1) / n_split;
416
416
  int64_t partial_total_size = arg.out_indexer.total_size * n_split;
@@ -423,9 +423,9 @@ TypeReduce* reduce_partial_pass(cumo_na_reduction_arg_t arg, cumo_reduce_addr_t
423
423
  int64_t shared_mem_size = sizeof(TypeReduce) * max_block_size;
424
424
 
425
425
  if (ad.in_out_flat && ad.in_reduce_flat) {
426
- reduction_partial_kernel<true,TypeIn,TypeReduce,ReductionImpl><<<grid_size, max_block_size, shared_mem_size>>>(arg, ad, partial, n_split, chunk, out_block_size, reduce_block_size, impl);
426
+ reduction_partial_kernel<true,ARG,TypeIn,TypeReduce,ReductionImpl><<<grid_size, max_block_size, shared_mem_size>>>(arg, ad, partial, n_split, chunk, out_block_size, reduce_block_size, impl);
427
427
  } else {
428
- reduction_partial_kernel<false,TypeIn,TypeReduce,ReductionImpl><<<grid_size, max_block_size, shared_mem_size>>>(arg, ad, partial, n_split, chunk, out_block_size, reduce_block_size, impl);
428
+ reduction_partial_kernel<false,ARG,TypeIn,TypeReduce,ReductionImpl><<<grid_size, max_block_size, shared_mem_size>>>(arg, ad, partial, n_split, chunk, out_block_size, reduce_block_size, impl);
429
429
  }
430
430
  cumo_cuda_runtime_check_kernel_launch();
431
431
 
@@ -439,7 +439,6 @@ TypeReduce* reduce_partial_pass(cumo_na_reduction_arg_t arg, cumo_reduce_addr_t
439
439
 
440
440
  } // cumo_detail
441
441
 
442
- // TODO(sonots): Optimize indexer by squashing (or reducing) dimensions
443
442
  template <typename TypeIn, typename TypeOut, typename ReductionImpl>
444
443
  void cumo_reduce(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
445
444
  if (arg.out_indexer.total_size == 0) {
@@ -581,4 +580,34 @@ void cumo_reduce_arg(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
581
580
  cumo_cuda_runtime_check_kernel_launch();
582
581
  }
583
582
 
583
+ // cumo_reduce_split for the arg form. The partials already carry indices along
584
+ // the reduce axis, so the second pass combines them with the plain kernel.
585
+ // Only for an impl whose Identity ignores its index argument.
586
+ template <typename TypeIn, typename TypeOut, typename ReductionImpl>
587
+ void cumo_reduce_arg_split(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
588
+ using TypeReduce = decltype(impl.Identity(0));
589
+
590
+ if (arg.out_indexer.total_size == 0) {
591
+ return;
592
+ }
593
+
594
+ int64_t reduce_total_size = arg.in_indexer.total_size / arg.out_indexer.total_size;
595
+ cumo_detail::cumo_reduce_addr_t ad = cumo_detail::make_reduce_addr(arg, reduce_total_size);
596
+
597
+ int64_t out_block_size, reduce_block_size;
598
+ cumo_detail::reduce_block_split(ad, reduce_total_size, &out_block_size, &reduce_block_size);
599
+ int64_t out_block_num = (arg.out_indexer.total_size + out_block_size - 1) / out_block_size;
600
+
601
+ int64_t n_split = cumo_detail::reduce_split_count(reduce_total_size, out_block_num);
602
+ if (n_split < 2) {
603
+ cumo_reduce_arg<TypeIn, TypeOut, ReductionImpl>(arg, std::forward<ReductionImpl>(impl));
604
+ return;
605
+ }
606
+
607
+ cumo_na_reduction_arg_t arg2 = arg;
608
+ TypeReduce* partial = cumo_detail::reduce_partial_pass<TypeIn, TypeReduce, ReductionImpl, true>(arg, ad, n_split, reduce_total_size, &arg2, impl);
609
+ cumo_reduce<TypeReduce, TypeOut, cumo_detail::reduce_combine<ReductionImpl>>(arg2, cumo_detail::reduce_combine<ReductionImpl>{impl});
610
+ cumo_cuda_runtime_free(reinterpret_cast<char*>(partial));
611
+ }
612
+
584
613
  #endif // CUMO_REDUCE_KERNEL_H
@@ -363,7 +363,7 @@ __host__ __device__ static inline dtype c_pow(dtype x, dtype y)
363
363
  }
364
364
 
365
365
  /* only internal use (called by c_pow_int) */
366
- __host__ __device__ static inline dtype c_pow_positive_int(dtype x, int p)
366
+ __host__ __device__ static inline dtype c_pow_positive_int(dtype x, unsigned int p)
367
367
  {
368
368
  dtype z = c_one();
369
369
  if (p==2) {return c_square(x);}
@@ -377,13 +377,15 @@ __host__ __device__ static inline dtype c_pow_positive_int(dtype x, int p)
377
377
  return z;
378
378
  }
379
379
 
380
+ // The magnitude is taken in unsigned, since -p overflows for INT_MIN and the
381
+ // wrapped negative would shift down to -1 and loop for ever.
380
382
  __host__ __device__ static inline dtype c_pow_int(dtype x, int p)
381
383
  {
382
384
  if (p<0) {
383
- x = c_pow_positive_int(x,-p);
385
+ x = c_pow_positive_int(x,-(unsigned int)p);
384
386
  return c_reciprocal(x);
385
387
  } else {
386
- return c_pow_positive_int(x,p);
388
+ return c_pow_positive_int(x,(unsigned int)p);
387
389
  }
388
390
  }
389
391
 
@@ -123,7 +123,7 @@ extern double pow(double, double);
123
123
  #define m_frexp(x,exp) frexp(x,exp)
124
124
 
125
125
  /* only internal use (called by pow_int) */
126
- __host__ __device__ static inline dtype pow_positive_int(dtype x, int p)
126
+ __host__ __device__ static inline dtype pow_positive_int(dtype x, unsigned int p)
127
127
  {
128
128
  dtype r=1;
129
129
  switch(p) {
@@ -133,7 +133,7 @@ __host__ __device__ static inline dtype pow_positive_int(dtype x, int p)
133
133
  case 3: return x*x*x;
134
134
  case 4: x=x*x; return x*x;
135
135
  }
136
- if (p>64) return pow(x,p);
136
+ if (p>64) return pow(x,(double)p);
137
137
  while (p) {
138
138
  if (p&1) r *= x;
139
139
  x *= x;
@@ -142,10 +142,12 @@ __host__ __device__ static inline dtype pow_positive_int(dtype x, int p)
142
142
  return r;
143
143
  }
144
144
 
145
+ // The magnitude is taken in unsigned, since -p overflows for INT_MIN and the
146
+ // wrapped negative would shift down to -1 and loop for ever.
145
147
  __host__ __device__ static inline dtype pow_int(dtype x, int p)
146
148
  {
147
- if (p<0) return 1/pow_positive_int(x, -p);
148
- return pow_positive_int(x, p);
149
+ if (p<0) return 1/pow_positive_int(x, -(unsigned int)p);
150
+ return pow_positive_int(x, (unsigned int)p);
149
151
  }
150
152
 
151
153
  __host__ __device__ static inline dtype f_seq(dtype x, dtype y, double c)
@@ -6,10 +6,6 @@
6
6
  #define m_sign(x) (((x)==0) ? 0 : (((x)>0) ? 1 : -1))
7
7
 
8
8
  __host__ __device__ static inline dtype m_abs(dtype x) {
9
- // TODO(sonots): How to handle in CUDA kernel?
10
- // if (x==DATA_MIN) {
11
- // rb_raise(cumo_na_eValueError, "cannot convert the minimum integer");
12
- // }
13
9
  return (x<0)?-x:x;
14
10
  }
15
11
 
@@ -1,6 +1,3 @@
1
- //TODO(sonots):
2
- //typedef VALUE dtype;
3
- //typedef VALUE rtype;
4
1
  typedef void* dtype;
5
2
  typedef void* rtype;
6
3
 
@@ -27,6 +27,7 @@ __device__ static dtype pow_int(dtype x, int p)
27
27
  case 2: return x*x;
28
28
  case 3: return x*x*x;
29
29
  }
30
+ if (p<0) return 0;
30
31
  while (p) {
31
32
  if (p&1) r *= x;
32
33
  x *= x;
@@ -10,8 +10,8 @@ extern "C" {
10
10
  #endif
11
11
  #endif
12
12
 
13
- #define CUMO_VERSION "0.5.9"
14
- #define CUMO_VERSION_CODE 59
13
+ #define CUMO_VERSION "0.5.10"
14
+ #define CUMO_VERSION_CODE 510
15
15
 
16
16
  bool cumo_compatible_mode_enabled_p();
17
17
  bool cumo_show_warning_enabled_p();
@@ -103,7 +103,7 @@ cumo_na_copy(VALUE self)
103
103
  // Everything else goes through the indexer so that ndloop hands the whole
104
104
  // view over at once: walking the outer dimensions itself costs a
105
105
  // synchronization per step when an operand carries an index array.
106
- if (rb_obj_class(self) != cumo_cRObject) {
106
+ if (!rb_obj_is_kind_of(self, cumo_cRObject)) {
107
107
  ndf.func = iter_copy_bytes_indexer;
108
108
  ndf.flag = CUMO_STRIDE_LOOP|CUMO_NDF_INDEXER_LOOP;
109
109
  }
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  def_id "cast"
4
+ def_id "_pending_scalar", "pending_scalar"
4
5
  def_id "mulsum"
5
6
  def_id "to_a"
6
7
  if is_complex