cumo 0.7.0 → 0.8.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/.rubocop_todo.yml +590 -152
- data/CHANGELOG.md +29 -0
- data/README.md +140 -0
- data/bench/cumo_probe.rb +1 -1
- data/cumo.gemspec +1 -1
- data/ext/cumo/cuda/cudnn.c +18 -0
- data/ext/cumo/cuda/cudnn_impl.cpp +60 -14
- data/ext/cumo/cuda/driver.c +11 -9
- data/ext/cumo/cuda/runtime.c +56 -0
- data/ext/cumo/cumo.c +4 -0
- data/ext/cumo/extconf.rb +2 -0
- data/ext/cumo/include/cumo/bit_reduce_kernel.h +6 -6
- data/ext/cumo/include/cumo/cuda/cudnn.h +40 -0
- data/ext/cumo/include/cumo/cuda/runtime.h +2 -0
- data/ext/cumo/include/cumo/intern.h +9 -0
- data/ext/cumo/include/cumo/narray.h +1 -0
- data/ext/cumo/include/cumo/reduce_kernel.h +32 -28
- data/ext/cumo/include/cumo/row_kernel.h +170 -0
- data/ext/cumo/include/cumo/row_method.h +48 -0
- data/ext/cumo/include/cumo/template_kernel.h +26 -0
- data/ext/cumo/include/cumo/types/bf16_def.h +82 -0
- data/ext/cumo/include/cumo/types/bf16_def_kernel.h +28 -0
- data/ext/cumo/include/cumo/types/bf16_macro.h +186 -0
- data/ext/cumo/include/cumo/types/bf16_macro_kernel.h +213 -0
- data/ext/cumo/include/cumo/types/bfloat.h +22 -0
- data/ext/cumo/include/cumo/types/bfloat_kernel.h +16 -0
- data/ext/cumo/include/cumo/types/bit.h +2 -0
- data/ext/cumo/include/cumo/types/bit_kernel.h +2 -0
- data/ext/cumo/include/cumo/types/complex_macro.h +2 -0
- data/ext/cumo/include/cumo/types/complex_macro_kernel.h +2 -0
- data/ext/cumo/include/cumo/types/float_def.h +8 -0
- data/ext/cumo/include/cumo/types/float_def_kernel.h +8 -0
- data/ext/cumo/include/cumo/types/float_macro.h +19 -17
- data/ext/cumo/include/cumo/types/float_macro_kernel.h +19 -19
- data/ext/cumo/include/cumo/types/half_macro.h +15 -17
- data/ext/cumo/include/cumo/types/half_macro_kernel.h +15 -0
- data/ext/cumo/include/cumo/types/robj_macro.h +2 -0
- data/ext/cumo/include/cumo/types/xint_macro.h +2 -6
- data/ext/cumo/include/cumo/types/xint_macro_kernel.h +2 -0
- data/ext/cumo/include/cumo.h +2 -2
- data/ext/cumo/narray/gen/def/bfloat.rb +56 -0
- data/ext/cumo/narray/gen/def/bit.rb +11 -1
- data/ext/cumo/narray/gen/def/dcomplex.rb +11 -1
- data/ext/cumo/narray/gen/def/dfloat.rb +10 -1
- data/ext/cumo/narray/gen/def/hfloat.rb +10 -1
- data/ext/cumo/narray/gen/def/int16.rb +11 -1
- data/ext/cumo/narray/gen/def/int32.rb +11 -1
- data/ext/cumo/narray/gen/def/int64.rb +11 -1
- data/ext/cumo/narray/gen/def/int8.rb +11 -1
- data/ext/cumo/narray/gen/def/robject.rb +11 -1
- data/ext/cumo/narray/gen/def/scomplex.rb +11 -1
- data/ext/cumo/narray/gen/def/sfloat.rb +10 -1
- data/ext/cumo/narray/gen/def/uint16.rb +11 -1
- data/ext/cumo/narray/gen/def/uint32.rb +11 -1
- data/ext/cumo/narray/gen/def/uint64.rb +11 -1
- data/ext/cumo/narray/gen/def/uint8.rb +11 -1
- data/ext/cumo/narray/gen/narray_def.rb +0 -5
- data/ext/cumo/narray/gen/spec.rb +11 -14
- data/ext/cumo/narray/gen/tmpl/accum_arg.c +1 -1
- data/ext/cumo/narray/gen/tmpl/accum_binary_kernel.cu +9 -9
- data/ext/cumo/narray/gen/tmpl/accum_index.c +1 -1
- data/ext/cumo/narray/gen/tmpl/batch_norm.c +3 -3
- data/ext/cumo/narray/gen/tmpl/batch_norm_backward.c +3 -3
- data/ext/cumo/narray/gen/tmpl/complex_accum_kernel.cu +15 -5
- data/ext/cumo/narray/gen/tmpl/conv.c +88 -121
- data/ext/cumo/narray/gen/tmpl/conv_grad_w.c +89 -81
- data/ext/cumo/narray/gen/tmpl/conv_transpose.c +88 -121
- data/ext/cumo/narray/gen/tmpl/cum.c +8 -7
- data/ext/cumo/narray/gen/tmpl/cum_kernel.cu +7 -6
- data/ext/cumo/narray/gen/tmpl/fixed_batch_norm.c +3 -3
- data/ext/cumo/narray/gen/tmpl/float_accum_kernel.cu +22 -10
- data/ext/cumo/narray/gen/tmpl/gemm.c +11 -35
- data/ext/cumo/narray/gen/tmpl/layer_norm.c +91 -0
- data/ext/cumo/narray/gen/tmpl/layer_norm_kernel.cu +54 -0
- data/ext/cumo/narray/gen/tmpl/lib.c +1 -0
- data/ext/cumo/narray/gen/tmpl/lib_kernel.cu +1 -0
- data/ext/cumo/narray/gen/tmpl/median.c +1 -1
- data/ext/cumo/narray/gen/tmpl/poly.c +0 -3
- data/ext/cumo/narray/gen/tmpl/poly_kernel.cu +1 -1
- data/ext/cumo/narray/gen/tmpl/pooling_backward.c +2 -1
- data/ext/cumo/narray/gen/tmpl/pooling_forward.c +2 -1
- data/ext/cumo/narray/gen/tmpl/rand.c +9 -9
- data/ext/cumo/narray/gen/tmpl/rand_kernel.cu +9 -9
- data/ext/cumo/narray/gen/tmpl/rand_norm.c +4 -4
- data/ext/cumo/narray/gen/tmpl/rand_norm_kernel.cu +7 -7
- data/ext/cumo/narray/gen/tmpl/real_accum_kernel.cu +6 -6
- data/ext/cumo/narray/gen/tmpl/softmax.c +56 -0
- data/ext/cumo/narray/gen/tmpl/softmax_kernel.cu +157 -0
- data/ext/cumo/narray/gen/tmpl/sort.c +9 -32
- data/ext/cumo/narray/gen/tmpl/sort_index.c +16 -95
- data/ext/cumo/narray/gen/tmpl/store_array.c +12 -9
- data/ext/cumo/narray/gen/tmpl_bit/mask.c +11 -11
- data/ext/cumo/narray/gen/tmpl_bit/store_array.c +5 -9
- data/ext/cumo/narray/gen/tmpl_bit/where.c +50 -11
- data/ext/cumo/narray/gen/tmpl_bit/where2.c +11 -12
- data/ext/cumo/narray/gen/tmpl_bit/where_kernel.cu +1 -2
- data/ext/cumo/narray/index.c +2 -1
- data/ext/cumo/narray/math.c +2 -0
- data/ext/cumo/narray/narray.c +27 -1
- data/ext/cumo/narray/ndloop.c +73 -4
- data/ext/cumo/narray/sort_kernel.cu +61 -41
- data/ext/cumo/narray/struct.c +3 -0
- data/lib/cumo/cuda/cudnn.rb +1 -1
- data/lib/cumo/narray/extra.rb +2 -2
- data/test/bfloat_test.rb +238 -0
- data/test/cuda/memory_pool_test.rb +8 -7
- data/test/cuda/runtime_test.rb +25 -0
- data/test/cudnn_test.rb +108 -24
- data/test/extra_test.rb +1 -1
- data/test/fused_test.rb +311 -0
- data/test/math_test.rb +77 -2
- data/test/narray_alt_coverage_test.rb +1 -0
- data/test/narray_test.rb +70 -10
- data/test/test_helper.rb +1 -0
- metadata +20 -4
- data/ext/cumo/narray/gen/tmpl/qsort.c +0 -205
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
# 0.8.0 (2026/09/16)
|
|
2
|
+
|
|
3
|
+
Breaking changes:
|
|
4
|
+
|
|
5
|
+
* `sort` and `sort_index` with `nan: true` put every NaN last. The host comparator they used answered -1 for a NaN against anything, so the partition carried elements other than the NaN to the wrong side: of 4156 cases over lengths 5 to 129, 2121 came back with the numbers out of order. `nan:` no longer changes the answer, and 1M DFloat costs 0.405 ms rather than 73.0 (PR #447)
|
|
6
|
+
* `conv` and `conv_transpose` refuse a bias that is not one value per output channel. A bias of length 1 was broadcast over every channel and answered as if it had been right, and a bias of two dimensions passed whenever its size fitted (PR #443)
|
|
7
|
+
* `var` and `stddev` answer NaN for a row holding an infinity, where the answer turned on where the infinity sat and how long the row was (PR #430)
|
|
8
|
+
|
|
9
|
+
Fixes:
|
|
10
|
+
|
|
11
|
+
* Fix every `dot` after `Cumo::CUDA::Runtime.cudaSetDevice` raising `CUBLAS_STATUS_EXECUTION_FAILED` for the life of the process. Cumo took a context of its own when it loaded, and `cudaSetDevice` binds the device's primary one, so the event inside the cuBLAS handle was left in a context nothing ran in (PR #451)
|
|
12
|
+
* Fix the device memory a rejected kernel launch left in the pool, `rb_raise` leaving by longjmp past the free below it. A sort lost 24MB and a convolution given a bias of the wrong class 5MB, every time; reductions, `softmax`, `layer_norm`, `Cumo::Bit#count`, `where`, `poly` and a store from a Ruby Array lost theirs the same way (PR #439, PR #438, PR #437, PR #436, PR #434, PR #433, PR #432, PR #431)
|
|
13
|
+
* Fix two cuDNN descriptor leaks, one where `batch_norm` raises after building its descriptors and one where a convolution finds no algorithm inside `CUMO_CUDNN_MAX_WORKSPACE_SIZE` (PR #441, PR #440)
|
|
14
|
+
* Fix a `std::bad_alloc` from the convolution algorithm cache killing the process, `cudnn_impl.cpp` being one `extern "C"` block with no handler in it (PR #444)
|
|
15
|
+
* Fix the nine cuDNN methods and `layer_norm` reading the wrong memory when `dup` answers a different class, a different shape or a non-contiguous view; `conv` took a reversed view and read past it (PR #425)
|
|
16
|
+
* Fix the spelling of "propagate" and "position" in the `nan:` documentation on `sort`, `sort_index`, `median`, `argmin`, `argmax`, `min_index` and `max_index` (PR #446)
|
|
17
|
+
|
|
18
|
+
Changes:
|
|
19
|
+
|
|
20
|
+
* Add `Cumo::BFloat`, the bfloat16 dtype, aliased `Cumo::BFloat16`. It keeps a float's exponent and gives up mantissa bits for it, so it holds the magnitudes a `Cumo::HFloat` overflows on, which is how published weights are written. Everything the dtype has is generated: the four operations, the comparisons, the reductions, `Cumo::NMath`, `sort`, `rand`, `dot` through cuBLAS and the cuDNN methods, both with a float compute type. `BFloat` and `HFloat` promote to `SFloat` when they meet, neither format containing the other. The accelerated paths want compute capability 8.0 (PR #455, PR #453, PR #452, PR #450)
|
|
21
|
+
* Add `layer_norm`, which normalizes a row in one kernel rather than the nine it is written out of, and `softmax`, which takes one rather than five. On SFloat `[1, 768]` the first goes from 19.9 to 2.9us and the second from 11.0 to 2.2, and `[4096, 768]` from 198.3 to 31.4 and 109.6 to 31.6. No shape is slower (PR #428, PR #426, PR #424, PR #423)
|
|
22
|
+
* Add `gelu` and `gelu_tanh` to `Cumo::NMath`. Two of them because there are two: `gelu` is the error function form PyTorch answers by default, and `gelu_tanh` the approximation GPT-2 and the models after it were trained with. They differ by 4.1e-4 at most, too little to tell apart by eye and far too much to swap under trained weights (PR #427)
|
|
23
|
+
* Take one reciprocal instead of two divides in the moments accumulator, a double divide being what this class of card charges most for; `var` goes from 172.7 to 106.5us in DFloat and `layer_norm` from 179.6 to 138.0 (PR #429)
|
|
24
|
+
* Store a Ruby Array into a strided or indexed view through one buffer held by the loop rather than one a row, a row having cost 2.53 times what the same payload written contiguously does and now costing 1.04 (PR #445)
|
|
25
|
+
* Stop `Cumo::RObject#poly` stopping the whole device once per element. It runs with `CUMO_NO_LOOP`, so ndloop calls its iterator once an element, and there is nothing left there to wait for (PR #435)
|
|
26
|
+
* Say once what `conv` and `conv_transpose` had each grown a copy of, the run struct and the bias block, and build the bias descriptor from a shape rather than lending the narray one (PR #442)
|
|
27
|
+
* Declare the gem's license as MIT. RubyGems reads the gemspec, which said BSD-3-Clause while the `LICENSE.txt` inside the same gem has carried the MIT text from the start (PR #448)
|
|
28
|
+
* Skip the CUDA build matrix for a README, license or benchmark edit no build step opens, and give ccache a ceiling one build fits in (PR #454, PR #449)
|
|
29
|
+
|
|
1
30
|
# 0.7.0 (2026/09/13)
|
|
2
31
|
|
|
3
32
|
Breaking changes:
|
data/README.md
CHANGED
|
@@ -222,6 +222,68 @@ Cumo::SFloat::Math.atan2(a, 2.0) #=> Cumo::SFloat
|
|
|
222
222
|
|
|
223
223
|
The 0-dimensional form has no effect under Numo, where `[]` returns a Ruby Float.
|
|
224
224
|
|
|
225
|
+
### Fused Operations
|
|
226
|
+
|
|
227
|
+
`layer_norm` and `softmax` normalize along the last axis in one kernel each.
|
|
228
|
+
Written out of the operators they take nine launches and five, and a launch costs about two microseconds whatever it is handed, so a short row pays for the launches rather than for its bytes.
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
y = x.layer_norm(gamma, beta, eps: 1e-5) # (x - mean) / sqrt(var + eps) * gamma + beta
|
|
232
|
+
probs = scores.softmax # exp(x - max) / sum(exp(x - max))
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
On an RTX 5070 Ti Laptop, against the same arithmetic spelled with operators, in microseconds:
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
layer_norm SFloat DFloat HFloat
|
|
239
|
+
shape fused written fused written fused written
|
|
240
|
+
1 x 768 3.9 30.1 9.7 30.3 3.9 29.8
|
|
241
|
+
256 x 768 6.6 22.5 22.0 35.4 7.8 22.4
|
|
242
|
+
4096 x 768 32.7 177.4 300.7 610.5 28.2 142.9
|
|
243
|
+
1 x 1000000 19.0 59.4 125.4 175.4 22.3 68.5
|
|
244
|
+
|
|
245
|
+
softmax SFloat DFloat HFloat
|
|
246
|
+
shape fused written fused written fused written
|
|
247
|
+
1 x 768 4.3 19.8 7.4 26.5 6.3 34.4
|
|
248
|
+
256 x 768 8.8 13.9 23.4 35.1 6.8 18.5
|
|
249
|
+
4096 x 768 49.2 122.7 318.3 549.0 28.2 109.8
|
|
250
|
+
1 x 1000000 27.8 44.0 108.6 148.5 32.1 53.8
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Both pay off in every precision, by the most where the row is short enough that the launches were all it was doing, and by the least in double, where the reduction itself costs more than the launches ever did.
|
|
254
|
+
The memory clock on this card steps between 9001 and 11001 MHz under a benchmark this short, and the absolute figures move with it.
|
|
255
|
+
The ratios hold across the steps.
|
|
256
|
+
|
|
257
|
+
Both methods normalize along the last axis only.
|
|
258
|
+
Other axes are reachable through `transpose`.
|
|
259
|
+
`layer_norm` divides the variance by the row length and not by one less, which is how the layer is defined and unlike `var`.
|
|
260
|
+
Both answer a new array and ignore `inplace!`, and a non-contiguous receiver is copied once rather than walked.
|
|
261
|
+
`gamma` and `beta` must be one-dimensional, as long as the last axis, and of the same class as the receiver.
|
|
262
|
+
|
|
263
|
+
`softmax` subtracts the row maximum before exponentiating, so a row masked with `-Float::INFINITY` answers zeros rather than `NaN`.
|
|
264
|
+
A row that is entirely `-Float::INFINITY` answers `NaN`, as the written-out form does.
|
|
265
|
+
|
|
266
|
+
Those two and `gelu_tanh` from the section below are where a transformer spends most of its launches.
|
|
267
|
+
Decoding one token of GPT-2 124M written against Numo's API takes 640 of them, and calling these three instead removes 332: 200 for the layer norms, 84 for the activations and 48 for the softmaxes.
|
|
268
|
+
|
|
269
|
+
### Two Spellings Of gelu
|
|
270
|
+
|
|
271
|
+
`Cumo::NMath` answers both definitions of the GELU activation, under names of
|
|
272
|
+
their own rather than a keyword, since a unary NMath function takes none.
|
|
273
|
+
|
|
274
|
+
| method | formula | matches |
|
|
275
|
+
| --- | --- | --- |
|
|
276
|
+
| `gelu` | `0.5 * x * (1 + erf(x / sqrt(2)))` | PyTorch's default, `approximate='none'` |
|
|
277
|
+
| `gelu_tanh` | `0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x**3)))` | PyTorch's `approximate='tanh'`, and what GPT-2 was trained with |
|
|
278
|
+
|
|
279
|
+
They differ by at most 4.1e-4, which is small enough to mistake one for the
|
|
280
|
+
other and far too large to swap under a set of trained weights, so pick the one
|
|
281
|
+
the weights were trained with.
|
|
282
|
+
|
|
283
|
+
Both follow PyTorch at the edges rather than the limit: a large negative `x`
|
|
284
|
+
runs out of significant digits and answers zero, and `-Float::INFINITY` answers
|
|
285
|
+
`NaN`.
|
|
286
|
+
|
|
225
287
|
### Half Precision
|
|
226
288
|
|
|
227
289
|
`Cumo::HFloat`, also reachable as `Cumo::Float16`, holds IEEE binary16: one sign bit, five of exponent and ten of mantissa.
|
|
@@ -262,6 +324,7 @@ That distinction is what makes half usable in a transformer's layer normalizatio
|
|
|
262
324
|
Squaring overflows at 256, since 256 squared is already past the top, and a residual stream with one outlier feature reaches thousands.
|
|
263
325
|
Writing the normalization out as `((x - mean) ** 2).mean` builds those squares as a half array and the answer is `Infinity`, while `x.var(axis: 1)` keeps them in its single-precision accumulator and answers 10208.0 against a single-precision 10209.
|
|
264
326
|
`var` saturates only if the variance itself is out of range, which is a far higher bar than any one deviation being over 256, but it is still a bar.
|
|
327
|
+
An input over 65504 is a different matter: it becomes an infinite element on the way in, and `var` and `stddev` answer `NaN` for a row holding one.
|
|
265
328
|
The quantity to check against 65504 is the largest variance a layer produces, not the largest activation in it.
|
|
266
329
|
A single outlier of size d among n values contributes only d squared over n to the variance, so a row 768 wide divides it by 768: an activation of 3000 squares to nine million but raises the variance of its row by about twelve thousand.
|
|
267
330
|
An activation that looks safe therefore says nothing about whether `var` overflows, in either direction.
|
|
@@ -342,6 +405,83 @@ Keeping the running statistics in single precision is what the arithmetic wants
|
|
|
342
405
|
Gradients are the other place half runs out of room.
|
|
343
406
|
Values below `Cumo::HFloat::MIN` of 6.1e-05 fall into the subnormals and then to zero, which is what loss scaling in a training loop exists to prevent.
|
|
344
407
|
|
|
408
|
+
### Brain Float
|
|
409
|
+
|
|
410
|
+
`Cumo::BFloat`, also reachable as `Cumo::BFloat16`, holds bfloat16: one sign bit, eight of exponent and seven of mantissa.
|
|
411
|
+
It is the other sixteen-bit float, and it differs from `Cumo::HFloat` in where those bits went.
|
|
412
|
+
A bfloat16 has the exponent of a `Cumo::SFloat` and three fewer mantissa bits than a binary16, so it reaches everything a single-precision value reaches and resolves less of it:
|
|
413
|
+
|
|
414
|
+
```ruby
|
|
415
|
+
Cumo::BFloat[1.0e38] #=> 9.969209968386869e+37
|
|
416
|
+
Cumo::HFloat[1.0e38] #=> Infinity
|
|
417
|
+
|
|
418
|
+
Cumo::BFloat[257.0] #=> 256.0
|
|
419
|
+
Cumo::HFloat[257.0] #=> 257.0
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Integers are exact only to 256, against 2048 for a binary16, and the largest finite value is 3.3895314e+38 rather than 65504.
|
|
423
|
+
That trade is why weights are published in it: a tensor that came out of training keeps its magnitudes, and the bits it loses are ones a trained weight does not carry.
|
|
424
|
+
|
|
425
|
+
The two do not contain each other, so an expression mixing them promotes to `Cumo::SFloat`, which does:
|
|
426
|
+
|
|
427
|
+
```ruby
|
|
428
|
+
(Cumo::BFloat[1.0] + Cumo::HFloat[1.0]).class #=> Cumo::SFloat
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Everything else promotes as `Cumo::SFloat` does, so an integer array or a Ruby Float mixed in stays bfloat16 while anything wider takes over.
|
|
432
|
+
|
|
433
|
+
`layer_norm`, `softmax` and both spellings of `gelu` take it, and so do the reductions, `sort`, `median`, `cumsum`, `rand` and `dot`.
|
|
434
|
+
The cuDNN methods take it too: `conv`, `conv_transpose`, `conv_grad_w`, `max_pool`, `avg_pool` and the three batch norm entries.
|
|
435
|
+
|
|
436
|
+
Reductions accumulate in single precision and round once at the end, exactly as they do for half, so a sum passes 256 without stopping there:
|
|
437
|
+
|
|
438
|
+
```ruby
|
|
439
|
+
Cumo::BFloat.new(40_000).fill(1.0).sum #=> 39936.0
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
That 39936 is 40000 rounded to the nearest bfloat16, whose values are 256 apart up there; a bfloat16 accumulator would have answered 256.
|
|
443
|
+
`sum`, `mean`, `var`, `stddev`, `rms`, `mulsum`, `dot`, `gemm` and `cumsum` all widen this way, and `prod` and `cumprod` do not, for the same reasons given under Half Precision.
|
|
444
|
+
|
|
445
|
+
The wider exponent moves where the saturation described there bites.
|
|
446
|
+
A variance is far from 3.4e+38, so `var` does not saturate on any input a network produces, and squaring is safe to 1.8e+19 rather than to 256.
|
|
447
|
+
What replaces it is the mantissa: a bfloat16 resolves about three decimal digits, so a normalization computes its statistics well enough while the values it writes back carry that resolution and no more.
|
|
448
|
+
|
|
449
|
+
#### What bfloat16 is faster at
|
|
450
|
+
|
|
451
|
+
`gemm` reaches the tensor cores and lands on the same throughput as half. Square matrices on an RTX 5070 Ti Laptop, median of five runs each, one process per case:
|
|
452
|
+
|
|
453
|
+
```
|
|
454
|
+
BFloat HFloat SFloat
|
|
455
|
+
1024x1024 0.044 ms 48.9 TF 0.044 ms 48.5 TF 0.144 ms 14.9 TF
|
|
456
|
+
2048x2048 0.298 ms 57.6 TF 0.303 ms 56.7 TF 1.404 ms 12.2 TF
|
|
457
|
+
4096x4096 2.839 ms 48.4 TF 2.898 ms 47.4 TF 9.540 ms 14.4 TF
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
The odd-column penalty is the same as half's and for the same reason, a row starting on a two-byte boundary.
|
|
461
|
+
1024x1024 times 1024x1024, with one dimension made odd at a time:
|
|
462
|
+
|
|
463
|
+
```
|
|
464
|
+
all even M odd K odd N odd
|
|
465
|
+
BFloat 48.9 TF 48.7 TF 23.5 TF 23.0 TF
|
|
466
|
+
HFloat 48.5 TF - 24.0 TF 23.1 TF
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
So the choice between the two sixteen-bit types is about range and resolution, not speed.
|
|
470
|
+
Take bfloat16 where the magnitudes came from somewhere else and binary16 where three more mantissa bits are worth having.
|
|
471
|
+
|
|
472
|
+
#### The accelerated paths want Ampere
|
|
473
|
+
|
|
474
|
+
cuBLAS supports `CUDA_R_16BF`, which is what `gemm` and `dot` reach, from compute capability 8.0.
|
|
475
|
+
Storing, casting and elementwise arithmetic have no such floor, because every operation is computed in single precision and rounded back, and they build and run wherever cumo does.
|
|
476
|
+
A `dot` on a pre-Ampere card is the one to expect trouble from.
|
|
477
|
+
**This is not measured here**: the only GPU these numbers came from is a Blackwell one, and the requirement is read from cuBLAS's documentation rather than reproduced.
|
|
478
|
+
|
|
479
|
+
cuDNN reaches bfloat16 as `CUDNN_DATA_BFLOAT16`, and its own bfloat16 kernels want Ampere for the same reason cuBLAS does.
|
|
480
|
+
A convolution is given `CUDNN_DATA_FLOAT` to accumulate in, so it passes 256 the way a reduction does.
|
|
481
|
+
The batch norm parameters are `Cumo::SFloat`, the same as they are for `Cumo::HFloat`.
|
|
482
|
+
The workspace ceiling matters here as much as it does for half: left at the default 8MB, a bfloat16 convolution is no faster than a single-precision one.
|
|
483
|
+
See [Raise the cuDNN workspace ceiling](#raise-the-cudnn-workspace-ceiling).
|
|
484
|
+
|
|
345
485
|
### Select a GPU device ID
|
|
346
486
|
|
|
347
487
|
Set the `CUDA_VISIBLE_DEVICES=id` environment variable, or
|
data/bench/cumo_probe.rb
CHANGED
|
@@ -312,7 +312,7 @@ op(:scalar, 'scalar_left') { |c| c.scalar - c.sc.inplace }
|
|
|
312
312
|
|
|
313
313
|
# NMath
|
|
314
314
|
%w[sqrt exp exp2 exp10 expm1 log log2 log10 log1p sin cos tan asin acos atan
|
|
315
|
-
sinh cosh tanh asinh acosh atanh cbrt erf erfc sinc].each do |f|
|
|
315
|
+
sinh cosh tanh asinh acosh atanh cbrt erf erfc sinc gelu gelu_tanh].each do |f|
|
|
316
316
|
op(:math, f, :float) { |c| XM::NMath.send(f, c.usc.inplace) }
|
|
317
317
|
end
|
|
318
318
|
op(:math, 'atan2', :float) { |c| XM::NMath.atan2(c.sc.inplace, c.b) }
|
data/cumo.gemspec
CHANGED
|
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
|
|
|
14
14
|
spec.summary = %q{Cumo is CUDA aware numerical library whose interface is highly compatible with Ruby Numo}
|
|
15
15
|
spec.description = %q{Cumo is CUDA aware numerical library whose interface is highly compatible with Ruby Numo.}
|
|
16
16
|
spec.homepage = "https://github.com/sonots/cumo"
|
|
17
|
-
spec.license = "
|
|
17
|
+
spec.license = "MIT"
|
|
18
18
|
|
|
19
19
|
spec.required_ruby_version = ">= 3.0.0"
|
|
20
20
|
|
data/ext/cumo/cuda/cudnn.c
CHANGED
|
@@ -53,6 +53,24 @@ BAD:
|
|
|
53
53
|
env, cudnn_max_workspace_size);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
VALUE
|
|
57
|
+
cumo_cuda_cudnn_release_conv_held(VALUE held)
|
|
58
|
+
{
|
|
59
|
+
cumo_cuda_cudnn_conv_held_t *h = (cumo_cuda_cudnn_conv_held_t*)held;
|
|
60
|
+
|
|
61
|
+
if (h->x_desc) { cudnnDestroyTensorDescriptor(h->x_desc); h->x_desc = 0; }
|
|
62
|
+
if (h->y_desc) { cudnnDestroyTensorDescriptor(h->y_desc); h->y_desc = 0; }
|
|
63
|
+
if (h->b_desc) { cudnnDestroyTensorDescriptor(h->b_desc); h->b_desc = 0; }
|
|
64
|
+
if (h->w_desc) { cudnnDestroyFilterDescriptor(h->w_desc); h->w_desc = 0; }
|
|
65
|
+
if (h->conv_desc) { cudnnDestroyConvolutionDescriptor(h->conv_desc); h->conv_desc = 0; }
|
|
66
|
+
// cuDNN is still reading the workspace, so the wait is the caller's to
|
|
67
|
+
// report. A free that raises from here would replace the exception being
|
|
68
|
+
// carried out, so it goes back the way an unwinding path has to.
|
|
69
|
+
cumo_cuda_runtime_return_scratch(h->workspace, h->workspace != NULL, &h->wait_status);
|
|
70
|
+
h->workspace = NULL;
|
|
71
|
+
return Qnil;
|
|
72
|
+
}
|
|
73
|
+
|
|
56
74
|
// Lazily initialize cudnn handle, and cache it
|
|
57
75
|
cudnnHandle_t
|
|
58
76
|
cumo_cuda_cudnn_handle()
|
|
@@ -67,17 +67,11 @@ cumo_cuda_cudnn_GetConvTransposeOutDim(
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
cudnnStatus_t
|
|
70
|
-
|
|
70
|
+
cumo_cuda_cudnn_CreateTensorDescriptorFromShape(
|
|
71
71
|
cudnnTensorDescriptor_t *desc,
|
|
72
|
-
|
|
72
|
+
int ndim, size_t *shape, cudnnDataType_t cudnn_dtype) {
|
|
73
73
|
cudnnStatus_t status = CUDNN_STATUS_SUCCESS;
|
|
74
|
-
cumo_narray_t *na;
|
|
75
|
-
CumoGetNArray(a, na);
|
|
76
|
-
int ndim = (int)(na->ndim);
|
|
77
|
-
size_t *shape = na->shape;
|
|
78
74
|
|
|
79
|
-
// only the shape is read: the descriptor is NCHW and the caller passes the
|
|
80
|
-
// contiguous copy, so a non-contiguous a still describes what is read
|
|
81
75
|
status = cudnnCreateTensorDescriptor(desc);
|
|
82
76
|
if (status != CUDNN_STATUS_SUCCESS) return status;
|
|
83
77
|
|
|
@@ -120,6 +114,47 @@ cumo_cuda_cudnn_CreateTensorDescriptor(
|
|
|
120
114
|
return status;
|
|
121
115
|
}
|
|
122
116
|
|
|
117
|
+
// Only the shape is read: the descriptor is NCHW and the caller passes the
|
|
118
|
+
// contiguous copy, so a non-contiguous a still describes what is read.
|
|
119
|
+
cudnnStatus_t
|
|
120
|
+
cumo_cuda_cudnn_CreateTensorDescriptor(
|
|
121
|
+
cudnnTensorDescriptor_t *desc,
|
|
122
|
+
VALUE a, cudnnDataType_t cudnn_dtype) {
|
|
123
|
+
cumo_narray_t *na;
|
|
124
|
+
CumoGetNArray(a, na);
|
|
125
|
+
return cumo_cuda_cudnn_CreateTensorDescriptorFromShape(desc, (int)(na->ndim), na->shape, cudnn_dtype);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// The bias is one value a channel, and cuDNN broadcasts it from a descriptor
|
|
129
|
+
// shaped like the output. Shared because conv and conv_transpose add it alike.
|
|
130
|
+
cudnnStatus_t
|
|
131
|
+
cumo_cuda_cudnn_AddBias(cumo_cuda_cudnn_conv_run_t *r, cudnnDataType_t dtype, const void *one) {
|
|
132
|
+
size_t bias_shape[CUMO_NA_MAX_DIMENSION];
|
|
133
|
+
cumo_narray_t *nb;
|
|
134
|
+
cudnnStatus_t status;
|
|
135
|
+
|
|
136
|
+
if (!RTEST(r->b_cont)) return CUDNN_STATUS_SUCCESS;
|
|
137
|
+
|
|
138
|
+
CumoGetNArray(r->b_cont, nb);
|
|
139
|
+
bias_shape[0] = 1;
|
|
140
|
+
bias_shape[1] = nb->size;
|
|
141
|
+
for (size_t i = 0; i < r->ndim; ++i) {
|
|
142
|
+
bias_shape[i + 2] = 1;
|
|
143
|
+
}
|
|
144
|
+
status = cumo_cuda_cudnn_CreateTensorDescriptorFromShape(
|
|
145
|
+
&r->held.b_desc, (int)(r->ndim + 2), bias_shape, dtype);
|
|
146
|
+
if (status != CUDNN_STATUS_SUCCESS) return status;
|
|
147
|
+
|
|
148
|
+
return cudnnAddTensor(
|
|
149
|
+
r->handle,
|
|
150
|
+
one,
|
|
151
|
+
r->held.b_desc,
|
|
152
|
+
(void*)r->b_cont_ptr,
|
|
153
|
+
one,
|
|
154
|
+
r->held.y_desc,
|
|
155
|
+
(void*)r->y_ptr);
|
|
156
|
+
}
|
|
157
|
+
|
|
123
158
|
cudnnStatus_t
|
|
124
159
|
cumo_cuda_cudnn_CreateFilterDescriptor(
|
|
125
160
|
cudnnFilterDescriptor_t *desc,
|
|
@@ -322,6 +357,17 @@ struct AlgoCacheEntry {
|
|
|
322
357
|
size_t memory;
|
|
323
358
|
cudnnMathType_t math_type;
|
|
324
359
|
};
|
|
360
|
+
|
|
361
|
+
// Nothing may reach the C caller: it has no handler, so an escaping exception
|
|
362
|
+
// is std::terminate. The cache only saves a later search, so one that cannot
|
|
363
|
+
// grow leaves the answer this call already has.
|
|
364
|
+
template <typename Map>
|
|
365
|
+
void StoreAlgoCache(Map& map, const AlgoCacheKey& key, const typename Map::mapped_type& entry) {
|
|
366
|
+
try {
|
|
367
|
+
map[key] = entry;
|
|
368
|
+
} catch (...) {
|
|
369
|
+
}
|
|
370
|
+
}
|
|
325
371
|
}
|
|
326
372
|
|
|
327
373
|
using FwdAlgoCacheMap = std::unordered_map<AlgoCacheKey, AlgoCacheEntry<cudnnConvolutionFwdAlgo_t>, AlgoCacheKeyHash>;
|
|
@@ -416,7 +462,7 @@ cumo_cuda_cudnn_FindConvolutionForwardAlgorithm(
|
|
|
416
462
|
perf_result,
|
|
417
463
|
(void*)workspace,
|
|
418
464
|
max_workspace_size);
|
|
419
|
-
|
|
465
|
+
cumo_cuda_runtime_return_scratch(workspace, 1, NULL);
|
|
420
466
|
if (status != CUDNN_STATUS_SUCCESS) return status;
|
|
421
467
|
// A search that answers success with nothing to report would leave
|
|
422
468
|
// perf_result untouched, and its algo and math type are used below.
|
|
@@ -424,7 +470,7 @@ cumo_cuda_cudnn_FindConvolutionForwardAlgorithm(
|
|
|
424
470
|
if (perf_result->status != CUDNN_STATUS_SUCCESS) return perf_result->status;
|
|
425
471
|
|
|
426
472
|
// TODO: thread-safe
|
|
427
|
-
algo_cache_map
|
|
473
|
+
StoreAlgoCache(algo_cache_map, key, {perf_result->algo, perf_result->memory, perf_result->mathType});
|
|
428
474
|
return status;
|
|
429
475
|
}
|
|
430
476
|
|
|
@@ -487,7 +533,7 @@ cumo_cuda_cudnn_FindConvolutionBackwardDataAlgorithm(
|
|
|
487
533
|
perf_result,
|
|
488
534
|
(void*)workspace,
|
|
489
535
|
max_workspace_size);
|
|
490
|
-
|
|
536
|
+
cumo_cuda_runtime_return_scratch(workspace, 1, NULL);
|
|
491
537
|
if (status != CUDNN_STATUS_SUCCESS) return status;
|
|
492
538
|
// A search that answers success with nothing to report would leave
|
|
493
539
|
// perf_result untouched, and its algo and math type are used below.
|
|
@@ -495,7 +541,7 @@ cumo_cuda_cudnn_FindConvolutionBackwardDataAlgorithm(
|
|
|
495
541
|
if (perf_result->status != CUDNN_STATUS_SUCCESS) return perf_result->status;
|
|
496
542
|
|
|
497
543
|
// TODO: thread-safe
|
|
498
|
-
algo_cache_map
|
|
544
|
+
StoreAlgoCache(algo_cache_map, key, {perf_result->algo, perf_result->memory, perf_result->mathType});
|
|
499
545
|
return status;
|
|
500
546
|
}
|
|
501
547
|
|
|
@@ -558,7 +604,7 @@ cumo_cuda_cudnn_FindConvolutionBackwardFilterAlgorithm(
|
|
|
558
604
|
perf_result,
|
|
559
605
|
(void*)workspace,
|
|
560
606
|
max_workspace_size);
|
|
561
|
-
|
|
607
|
+
cumo_cuda_runtime_return_scratch(workspace, 1, NULL);
|
|
562
608
|
if (status != CUDNN_STATUS_SUCCESS) return status;
|
|
563
609
|
// A search that answers success with nothing to report would leave
|
|
564
610
|
// perf_result untouched, and its algo and math type are used below.
|
|
@@ -566,7 +612,7 @@ cumo_cuda_cudnn_FindConvolutionBackwardFilterAlgorithm(
|
|
|
566
612
|
if (perf_result->status != CUDNN_STATUS_SUCCESS) return perf_result->status;
|
|
567
613
|
|
|
568
614
|
// TODO: thread-safe
|
|
569
|
-
algo_cache_map
|
|
615
|
+
StoreAlgoCache(algo_cache_map, key, {perf_result->algo, perf_result->memory, perf_result->mathType});
|
|
570
616
|
return status;
|
|
571
617
|
}
|
|
572
618
|
|
data/ext/cumo/cuda/driver.c
CHANGED
|
@@ -449,15 +449,17 @@ Init_cumo_cuda_driver()
|
|
|
449
449
|
|
|
450
450
|
// A driver API call needs a current context, and the runtime API only
|
|
451
451
|
// creates its primary one once an array operation happens, so this covers
|
|
452
|
-
// the gap in between.
|
|
453
|
-
//
|
|
454
|
-
//
|
|
455
|
-
//
|
|
452
|
+
// the gap in between. It has to be the primary context and not one of our
|
|
453
|
+
// own: cudaSetDevice binds the primary one, so anything built in a
|
|
454
|
+
// different context stops working the moment a caller sets the device,
|
|
455
|
+
// including the events a cuBLAS handle keeps inside itself.
|
|
456
|
+
// Losing it is not fatal -- everything but a driver call made before any
|
|
457
|
+
// array operation still works -- so a device that refuses a context is
|
|
458
|
+
// left for that call to report. cuDeviceGet leaves cuDevice untouched
|
|
459
|
+
// when it fails, hence the guard.
|
|
456
460
|
if (cuDeviceGet(&cuDevice, 0) == CUDA_SUCCESS) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
cuCtxCreate(&context, 0, cuDevice);
|
|
461
|
-
#endif
|
|
461
|
+
if (cuDevicePrimaryCtxRetain(&context, cuDevice) == CUDA_SUCCESS) {
|
|
462
|
+
cuCtxSetCurrent(context);
|
|
463
|
+
}
|
|
462
464
|
}
|
|
463
465
|
}
|
data/ext/cumo/cuda/runtime.c
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
#include <assert.h>
|
|
3
3
|
#include <cuda_runtime.h>
|
|
4
4
|
#include "cumo/cuda/runtime.h"
|
|
5
|
+
#include "cumo/cuda/memory_pool.h"
|
|
6
|
+
#include "cumo/template_kernel.h"
|
|
5
7
|
|
|
6
8
|
VALUE cumo_cuda_eRuntimeError;
|
|
7
9
|
VALUE cumo_cuda_mRuntime;
|
|
@@ -20,6 +22,60 @@ cumo_cuda_runtime_check_kernel_launch(void)
|
|
|
20
22
|
check_status(cudaGetLastError());
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
// A pointer given twice would be freed twice, and the second free would reach
|
|
26
|
+
// past the pool to a block it still owns, so each one goes back once.
|
|
27
|
+
static void
|
|
28
|
+
release_held(char *p[5])
|
|
29
|
+
{
|
|
30
|
+
int i, j;
|
|
31
|
+
for (i = 0; i < 5; ++i) {
|
|
32
|
+
if (p[i] == NULL) { continue; }
|
|
33
|
+
for (j = 0; j < i; ++j) {
|
|
34
|
+
if (p[j] == p[i]) { break; }
|
|
35
|
+
}
|
|
36
|
+
if (j == i) { cumo_cuda_runtime_free_no_raise(p[i]); }
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// What a launcher holding scratch has to call instead of the plain check, for a
|
|
41
|
+
// status it already took out of the slot: a library call in the window takes it
|
|
42
|
+
// first otherwise, and whoever reads it first is the only one who sees it.
|
|
43
|
+
// Raising is a longjmp, so the frees written below the check never run, and
|
|
44
|
+
// unwinding frees through the form that cannot raise so one failure cannot hide
|
|
45
|
+
// another.
|
|
46
|
+
void
|
|
47
|
+
cumo_cuda_runtime_check_taken_status_holding(int status, char *p0, char *p1, char *p2, char *p3, char *p4)
|
|
48
|
+
{
|
|
49
|
+
if ((cudaError_t)status != cudaSuccess) {
|
|
50
|
+
char *held[5];
|
|
51
|
+
held[0] = p0; held[1] = p1; held[2] = p2; held[3] = p3; held[4] = p4;
|
|
52
|
+
release_held(held);
|
|
53
|
+
}
|
|
54
|
+
check_status((cudaError_t)status);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The slot is read once here rather than peeked and read again: the frees below
|
|
58
|
+
// can put a status of their own in it, and the launch is the one to report.
|
|
59
|
+
void
|
|
60
|
+
cumo_cuda_runtime_check_kernel_launch_holding(char *p0, char *p1, char *p2, char *p3, char *p4)
|
|
61
|
+
{
|
|
62
|
+
cumo_cuda_runtime_check_taken_status_holding((int)cudaGetLastError(), p0, p1, p2, p3, p4);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// How scratch goes back from a handler that may be unwinding: a free that
|
|
66
|
+
// raises there replaces the exception being carried, and leaves every free
|
|
67
|
+
// below it unrun. wait_for_stream asks for the wait a caller needs when the
|
|
68
|
+
// buffer is still being read, since the pool hands a freed chunk straight out.
|
|
69
|
+
void
|
|
70
|
+
cumo_cuda_runtime_return_scratch(char *ptr, int wait_for_stream, cudaError_t *status)
|
|
71
|
+
{
|
|
72
|
+
if (wait_for_stream) {
|
|
73
|
+
cudaError_t wait = cudaStreamSynchronize(0);
|
|
74
|
+
if (status != NULL && *status == cudaSuccess) { *status = wait; }
|
|
75
|
+
}
|
|
76
|
+
if (ptr != NULL) { cumo_cuda_runtime_free_no_raise(ptr); }
|
|
77
|
+
}
|
|
78
|
+
|
|
23
79
|
int*
|
|
24
80
|
cumo_cuda_runtime_error_flag_ptr(void)
|
|
25
81
|
{
|
data/ext/cumo/cumo.c
CHANGED
|
@@ -21,6 +21,7 @@ void Init_cumo_uint16();
|
|
|
21
21
|
void Init_cumo_uint32();
|
|
22
22
|
void Init_cumo_uint64();
|
|
23
23
|
void Init_cumo_hfloat();
|
|
24
|
+
void Init_cumo_bfloat();
|
|
24
25
|
void Init_cumo_sfloat();
|
|
25
26
|
void Init_cumo_scomplex();
|
|
26
27
|
void Init_cumo_dfloat();
|
|
@@ -31,6 +32,7 @@ void Init_cumo_dfloat_upcast();
|
|
|
31
32
|
void Init_cumo_scomplex_upcast();
|
|
32
33
|
void Init_cumo_sfloat_upcast();
|
|
33
34
|
void Init_cumo_hfloat_upcast();
|
|
35
|
+
void Init_cumo_bfloat_upcast();
|
|
34
36
|
void Init_cumo_int64_upcast();
|
|
35
37
|
void Init_cumo_uint64_upcast();
|
|
36
38
|
void Init_cumo_int32_upcast();
|
|
@@ -167,6 +169,7 @@ Init_cumo()
|
|
|
167
169
|
Init_cumo_scomplex();
|
|
168
170
|
Init_cumo_sfloat();
|
|
169
171
|
Init_cumo_hfloat();
|
|
172
|
+
Init_cumo_bfloat();
|
|
170
173
|
|
|
171
174
|
Init_cumo_int64();
|
|
172
175
|
Init_cumo_uint64();
|
|
@@ -186,6 +189,7 @@ Init_cumo()
|
|
|
186
189
|
Init_cumo_scomplex_upcast();
|
|
187
190
|
Init_cumo_sfloat_upcast();
|
|
188
191
|
Init_cumo_hfloat_upcast();
|
|
192
|
+
Init_cumo_bfloat_upcast();
|
|
189
193
|
Init_cumo_int64_upcast();
|
|
190
194
|
Init_cumo_uint64_upcast();
|
|
191
195
|
Init_cumo_int32_upcast();
|
data/ext/cumo/extconf.rb
CHANGED
|
@@ -65,6 +65,7 @@ narray/types/uint16
|
|
|
65
65
|
narray/types/uint32
|
|
66
66
|
narray/types/uint64
|
|
67
67
|
narray/types/hfloat
|
|
68
|
+
narray/types/bfloat
|
|
68
69
|
narray/types/sfloat
|
|
69
70
|
narray/types/dfloat
|
|
70
71
|
narray/types/scomplex
|
|
@@ -80,6 +81,7 @@ narray/types/uint16_kernel
|
|
|
80
81
|
narray/types/uint32_kernel
|
|
81
82
|
narray/types/uint64_kernel
|
|
82
83
|
narray/types/hfloat_kernel
|
|
84
|
+
narray/types/bfloat_kernel
|
|
83
85
|
narray/types/sfloat_kernel
|
|
84
86
|
narray/types/dfloat_kernel
|
|
85
87
|
narray/types/scomplex_kernel
|
|
@@ -442,7 +442,7 @@ static inline void cumo_bit_count_reduce(cumo_na_bit_reduction_arg_t arg, int in
|
|
|
442
442
|
cumo_bit_detail::bit_count_partial_kernel<<<grid_size, block_size, shared_mem_size>>>(
|
|
443
443
|
arg, p.ad, p.wa, invert, partial, p.n_split, p.chunk,
|
|
444
444
|
(int)p.split_out_block_size, (int)p.split_reduce_block_size, p.unit_total_size);
|
|
445
|
-
|
|
445
|
+
cumo_check_launch_holding(partial);
|
|
446
446
|
|
|
447
447
|
cumo_na_reduction_arg_t combine;
|
|
448
448
|
combine.in.ptr = reinterpret_cast<char*>(partial);
|
|
@@ -452,7 +452,7 @@ static inline void cumo_bit_count_reduce(cumo_na_bit_reduction_arg_t arg, int in
|
|
|
452
452
|
combine.in_indexer.total_size = partial_total_size;
|
|
453
453
|
combine.out = arg.out;
|
|
454
454
|
combine.out_indexer = arg.out_indexer;
|
|
455
|
-
cumo_reduce<uint64_t, uint64_t>(combine, cumo_bit_detail::BitCountImpl{});
|
|
455
|
+
cumo_reduce<uint64_t, uint64_t>(combine, cumo_bit_detail::BitCountImpl{}, reinterpret_cast<char*>(partial));
|
|
456
456
|
|
|
457
457
|
cumo_cuda_runtime_free(reinterpret_cast<char*>(partial));
|
|
458
458
|
}
|
|
@@ -483,12 +483,12 @@ static inline void cumo_bit_stat_reduce(cumo_na_bit_reduction_arg_t arg, int sta
|
|
|
483
483
|
cumo_bit_detail::bit_count_partial_kernel<<<grid_size, block_size, shared_mem_size>>>(
|
|
484
484
|
arg, p.ad, p.wa, 0, partial, p.n_split, p.chunk,
|
|
485
485
|
(int)p.split_out_block_size, (int)p.split_reduce_block_size, p.unit_total_size);
|
|
486
|
-
|
|
486
|
+
cumo_check_launch_holding(partial);
|
|
487
487
|
|
|
488
488
|
int64_t combine_grid = (p.out_total_size + block_size - 1) / block_size;
|
|
489
489
|
if (combine_grid > cumo_detail::max_grid_size) combine_grid = cumo_detail::max_grid_size;
|
|
490
490
|
cumo_bit_detail::bit_stat_combine_kernel<<<combine_grid, block_size>>>(arg, p.ad, stat, partial, p.n_split);
|
|
491
|
-
|
|
491
|
+
cumo_check_launch_holding(partial);
|
|
492
492
|
|
|
493
493
|
cumo_cuda_runtime_free(reinterpret_cast<char*>(partial));
|
|
494
494
|
}
|
|
@@ -519,12 +519,12 @@ static inline void cumo_bit_pred_reduce(cumo_na_bit_pred_reduction_arg_t arg, in
|
|
|
519
519
|
cumo_bit_detail::bit_count_partial_kernel<<<grid_size, block_size, shared_mem_size>>>(
|
|
520
520
|
arg, p.ad, p.wa, 0, partial, p.n_split, p.chunk,
|
|
521
521
|
(int)p.split_out_block_size, (int)p.split_reduce_block_size, p.unit_total_size);
|
|
522
|
-
|
|
522
|
+
cumo_check_launch_holding(partial);
|
|
523
523
|
|
|
524
524
|
int64_t combine_grid = (p.out_total_size + block_size - 1) / block_size;
|
|
525
525
|
if (combine_grid > cumo_detail::max_grid_size) combine_grid = cumo_detail::max_grid_size;
|
|
526
526
|
cumo_bit_detail::bit_pred_combine_kernel<<<combine_grid, block_size>>>(arg, p.ad, all, partial, p.n_split);
|
|
527
|
-
|
|
527
|
+
cumo_check_launch_holding(partial);
|
|
528
528
|
|
|
529
529
|
cumo_cuda_runtime_free(reinterpret_cast<char*>(partial));
|
|
530
530
|
}
|
|
@@ -97,9 +97,42 @@ cumo_cuda_cudnn_check_param_type(VALUE param, VALUE type, const char* name)
|
|
|
97
97
|
void
|
|
98
98
|
cumo_cuda_cudnn_check_status(cudnnStatus_t status);
|
|
99
99
|
|
|
100
|
+
// Raises, so the methods that clean up through a label take it before they
|
|
101
|
+
// hold a descriptor: a raise runs no label.
|
|
100
102
|
cudnnHandle_t
|
|
101
103
|
cumo_cuda_cudnn_handle();
|
|
102
104
|
|
|
105
|
+
// What a convolution holds while it works. A raise runs no error label, so the
|
|
106
|
+
// work goes under rb_ensure and the handler gives these back instead.
|
|
107
|
+
typedef struct {
|
|
108
|
+
cudnnTensorDescriptor_t x_desc;
|
|
109
|
+
cudnnTensorDescriptor_t y_desc;
|
|
110
|
+
cudnnTensorDescriptor_t b_desc;
|
|
111
|
+
cudnnFilterDescriptor_t w_desc;
|
|
112
|
+
cudnnConvolutionDescriptor_t conv_desc;
|
|
113
|
+
char *workspace;
|
|
114
|
+
cudaError_t wait_status;
|
|
115
|
+
} cumo_cuda_cudnn_conv_held_t;
|
|
116
|
+
|
|
117
|
+
// Takes the held as its argument so it can be handed to rb_ensure as it is.
|
|
118
|
+
VALUE
|
|
119
|
+
cumo_cuda_cudnn_release_conv_held(VALUE held);
|
|
120
|
+
|
|
121
|
+
// What conv and conv_transpose carry into the run the ensure above guards.
|
|
122
|
+
typedef struct {
|
|
123
|
+
cumo_cuda_cudnn_conv_held_t held;
|
|
124
|
+
cudnnHandle_t handle;
|
|
125
|
+
VALUE x_cont, w_cont, y, b_cont;
|
|
126
|
+
char *x_cont_ptr, *w_cont_ptr, *y_ptr, *b_cont_ptr;
|
|
127
|
+
size_t ndim;
|
|
128
|
+
int *int_stride, *int_pad;
|
|
129
|
+
cudnnStatus_t status;
|
|
130
|
+
} cumo_cuda_cudnn_conv_run_t;
|
|
131
|
+
|
|
132
|
+
// Adds nothing when the run carries no bias, so callers need no guard.
|
|
133
|
+
cudnnStatus_t
|
|
134
|
+
cumo_cuda_cudnn_AddBias(cumo_cuda_cudnn_conv_run_t *r, cudnnDataType_t dtype, const void *one);
|
|
135
|
+
|
|
103
136
|
// VALUE is Ruby Array
|
|
104
137
|
static inline void
|
|
105
138
|
cumo_cuda_cudnn_get_int_ary(int* int_ary, VALUE ary, size_t ndim, int default_value)
|
|
@@ -178,6 +211,13 @@ cumo_cuda_cudnn_CreateTensorDescriptor(
|
|
|
178
211
|
VALUE a,
|
|
179
212
|
cudnnDataType_t cudnn_dtype);
|
|
180
213
|
|
|
214
|
+
cudnnStatus_t
|
|
215
|
+
cumo_cuda_cudnn_CreateTensorDescriptorFromShape(
|
|
216
|
+
cudnnTensorDescriptor_t *desc,
|
|
217
|
+
int ndim,
|
|
218
|
+
size_t *shape,
|
|
219
|
+
cudnnDataType_t cudnn_dtype);
|
|
220
|
+
|
|
181
221
|
cudnnStatus_t
|
|
182
222
|
cumo_cuda_cudnn_CreateFilterDescriptor(
|
|
183
223
|
cudnnFilterDescriptor_t *desc,
|
|
@@ -66,6 +66,15 @@ VALUE cumo_na_ndloop4(cumo_ndfunc_t *nf, void *ptr, VALUE args);
|
|
|
66
66
|
|
|
67
67
|
VALUE cumo_na_ndloop_cast_narray_to_rarray(cumo_ndfunc_t *nf, VALUE nary, VALUE fmt);
|
|
68
68
|
VALUE cumo_na_ndloop_store_rarray(cumo_ndfunc_t *nf, VALUE nary, VALUE rary);
|
|
69
|
+
|
|
70
|
+
// One device buffer for a whole walk, not one per row. A row that gives its
|
|
71
|
+
// own back has to wait for the kernel still reading it, and that wait costs
|
|
72
|
+
// more than everything else the row does. cumo_na_ndloop_store_rarray owns
|
|
73
|
+
// one and hands it over as the loop's opt_ptr.
|
|
74
|
+
// Opaque: an iterator asks for a buffer and reads nothing else.
|
|
75
|
+
typedef struct cumo_na_stage_pool cumo_na_stage_pool_t;
|
|
76
|
+
|
|
77
|
+
char *cumo_na_stage_pool_get(cumo_na_stage_pool_t *pool, size_t bytes);
|
|
69
78
|
VALUE cumo_na_ndloop_store_rarray2(cumo_ndfunc_t *nf, VALUE nary, VALUE rary, VALUE opt);
|
|
70
79
|
VALUE cumo_na_ndloop_inspect(VALUE nary, cumo_na_text_func_t func, VALUE opt);
|
|
71
80
|
VALUE cumo_na_ndloop_with_index(cumo_ndfunc_t *nf, int argc, ...);
|