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.
- checksums.yaml +4 -4
- data/3rd_party/mkmf-cu/lib/mkmf-cu/cli.rb +3 -1
- data/CHANGELOG.md +40 -0
- data/Gemfile +1 -0
- data/README.md +66 -7
- data/ext/cumo/cuda/cudnn_impl.cpp +1 -1
- data/ext/cumo/cuda/memory_pool_impl.cpp +6 -1
- data/ext/cumo/cuda/memory_pool_impl_test.cpp +47 -0
- data/ext/cumo/extconf.rb +30 -1
- data/ext/cumo/include/cumo/cuda/cudnn.h +53 -0
- data/ext/cumo/include/cumo/indexer.h +21 -1
- data/ext/cumo/include/cumo/narray.h +4 -1
- data/ext/cumo/include/cumo/reduce_kernel.h +35 -6
- data/ext/cumo/include/cumo/types/complex_kernel.h +5 -3
- data/ext/cumo/include/cumo/types/float_macro_kernel.h +6 -4
- data/ext/cumo/include/cumo/types/int_macro_kernel.h +0 -4
- data/ext/cumo/include/cumo/types/robject_kernel.h +0 -3
- data/ext/cumo/include/cumo/types/uint_macro_kernel.h +1 -0
- data/ext/cumo/include/cumo.h +2 -2
- data/ext/cumo/narray/data.c +1 -1
- data/ext/cumo/narray/gen/spec.rb +1 -0
- data/ext/cumo/narray/gen/tmpl/accum.c +2 -11
- data/ext/cumo/narray/gen/tmpl/accum_arg.c +8 -14
- data/ext/cumo/narray/gen/tmpl/accum_arg_kernel.cu +104 -22
- data/ext/cumo/narray/gen/tmpl/accum_index.c +7 -26
- data/ext/cumo/narray/gen/tmpl/accum_index_kernel.cu +104 -22
- data/ext/cumo/narray/gen/tmpl/allocate.c +19 -0
- data/ext/cumo/narray/gen/tmpl/aset.c +8 -0
- data/ext/cumo/narray/gen/tmpl/batch_norm.c +8 -6
- data/ext/cumo/narray/gen/tmpl/batch_norm_backward.c +18 -8
- data/ext/cumo/narray/gen/tmpl/binary.c +76 -0
- data/ext/cumo/narray/gen/tmpl/binary_kernel.cu +57 -0
- data/ext/cumo/narray/gen/tmpl/binary_s.c +68 -9
- data/ext/cumo/narray/gen/tmpl/binary_s_kernel.cu +44 -7
- data/ext/cumo/narray/gen/tmpl/cast.c +6 -0
- data/ext/cumo/narray/gen/tmpl/clip.c +67 -0
- data/ext/cumo/narray/gen/tmpl/clip_kernel.cu +63 -17
- data/ext/cumo/narray/gen/tmpl/complex_accum_kernel.cu +145 -0
- data/ext/cumo/narray/gen/tmpl/cond_binary.c +53 -0
- data/ext/cumo/narray/gen/tmpl/cond_binary_kernel.cu +29 -6
- data/ext/cumo/narray/gen/tmpl/conv.c +7 -5
- data/ext/cumo/narray/gen/tmpl/conv_grad_w.c +13 -10
- data/ext/cumo/narray/gen/tmpl/conv_transpose.c +8 -7
- data/ext/cumo/narray/gen/tmpl/fixed_batch_norm.c +8 -6
- data/ext/cumo/narray/gen/tmpl/float_accum_kernel.cu +120 -0
- data/ext/cumo/narray/gen/tmpl/gemm.c +82 -8
- data/ext/cumo/narray/gen/tmpl/minmax.c +6 -7
- data/ext/cumo/narray/gen/tmpl/new_dim0.c +17 -0
- data/ext/cumo/narray/gen/tmpl/pooling_backward.c +18 -3
- data/ext/cumo/narray/gen/tmpl/pooling_forward.c +7 -5
- data/ext/cumo/narray/gen/tmpl/pow.c +91 -0
- data/ext/cumo/narray/gen/tmpl/pow_kernel.cu +60 -12
- data/ext/cumo/narray/gen/tmpl/real_accum_kernel.cu +87 -0
- data/ext/cumo/narray/gen/tmpl/seq_kernel.cu +13 -1
- data/ext/cumo/narray/gen/tmpl/sort_index.c +5 -2
- data/ext/cumo/narray/gen/tmpl_bit/aset.c +8 -0
- data/ext/cumo/narray/gen/tmpl_bit/mask.c +20 -5
- data/ext/cumo/narray/gen/tmpl_bit/mask_kernel.cu +54 -0
- data/ext/cumo/narray/index.c +19 -3
- data/ext/cumo/narray/index_kernel.cu +4 -4
- data/ext/cumo/narray/narray.c +16 -4
- data/ext/cumo/narray/ndloop.c +0 -1
- data/test/bit_test.rb +20 -4
- data/test/cudnn_test.rb +134 -0
- data/test/cumo_test.rb +59 -0
- data/test/narray_test.rb +1066 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bcb51638cb488e5abcb0bf885744ea1478f4daefd4945f6af8becf252bc85f29
|
|
4
|
+
data.tar.gz: 75aed5ea382f37a823992e29ef2f3fe9ee31a7b7c11350d2541e5f4fc0c0fb59
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
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
|
-
|
|
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
|
-
|
|
103
|
-
* `[]`
|
|
104
|
-
* `count_true`
|
|
105
|
-
* `count_false`
|
|
105
|
+
The methods affected are:
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
|
data/ext/cumo/include/cumo.h
CHANGED
data/ext/cumo/narray/data.c
CHANGED
|
@@ -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 (
|
|
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
|
}
|