cumo 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +85 -0
  3. data/README.md +515 -19
  4. data/bench/cumo_probe.rb +6 -2
  5. data/bench/cumo_sync_probe.rb +1 -1
  6. data/cumo.gemspec +7 -1
  7. data/ext/cumo/cuda/cublas.c +6 -1
  8. data/ext/cumo/cuda/cudnn.c +5 -1
  9. data/ext/cumo/cuda/cudnn_impl.cpp +29 -3
  10. data/ext/cumo/cuda/driver.c +158 -1
  11. data/ext/cumo/cuda/memory_pool.cpp +3 -7
  12. data/ext/cumo/cuda/memory_pool_impl.cpp +32 -22
  13. data/ext/cumo/cuda/memory_pool_impl.hpp +3 -1
  14. data/ext/cumo/cuda/memory_pool_impl_test.cpp +19 -25
  15. data/ext/cumo/cuda/nvrtc.c +42 -0
  16. data/ext/cumo/cuda/runtime.c +627 -1
  17. data/ext/cumo/cumo.c +43 -12
  18. data/ext/cumo/include/cumo/bit_reduce_kernel.h +8 -8
  19. data/ext/cumo/include/cumo/check.h +11 -3
  20. data/ext/cumo/include/cumo/cuda/cudnn.h +17 -11
  21. data/ext/cumo/include/cumo/cuda/runtime.h +77 -3
  22. data/ext/cumo/include/cumo/indexer.h +16 -0
  23. data/ext/cumo/include/cumo/intern.h +10 -1
  24. data/ext/cumo/include/cumo/ndloop.h +21 -0
  25. data/ext/cumo/include/cumo/reduce_kernel.h +261 -36
  26. data/ext/cumo/include/cumo/row_kernel.h +49 -17
  27. data/ext/cumo/include/cumo/row_method.h +101 -0
  28. data/ext/cumo/include/cumo/template_kernel.h +4 -1
  29. data/ext/cumo/include/cumo/types/bf16_macro.h +6 -178
  30. data/ext/cumo/include/cumo/types/bf16_macro_kernel.h +6 -200
  31. data/ext/cumo/include/cumo/types/f16_macro.h +213 -0
  32. data/ext/cumo/include/cumo/types/f16_macro_kernel.h +236 -0
  33. data/ext/cumo/include/cumo/types/float_macro.h +29 -0
  34. data/ext/cumo/include/cumo/types/float_macro_kernel.h +29 -0
  35. data/ext/cumo/include/cumo/types/half_macro.h +4 -176
  36. data/ext/cumo/include/cumo/types/half_macro_kernel.h +4 -198
  37. data/ext/cumo/include/cumo.h +9 -2
  38. data/ext/cumo/narray/data.c +40 -8
  39. data/ext/cumo/narray/data_kernel.cu +131 -21
  40. data/ext/cumo/narray/gen/def/bfloat.rb +2 -3
  41. data/ext/cumo/narray/gen/def/bit.rb +1 -3
  42. data/ext/cumo/narray/gen/def/dcomplex.rb +2 -3
  43. data/ext/cumo/narray/gen/def/dfloat.rb +2 -3
  44. data/ext/cumo/narray/gen/def/hfloat.rb +2 -3
  45. data/ext/cumo/narray/gen/def/int16.rb +1 -3
  46. data/ext/cumo/narray/gen/def/int32.rb +1 -3
  47. data/ext/cumo/narray/gen/def/int64.rb +1 -3
  48. data/ext/cumo/narray/gen/def/int8.rb +1 -3
  49. data/ext/cumo/narray/gen/def/robject.rb +1 -3
  50. data/ext/cumo/narray/gen/def/scomplex.rb +2 -3
  51. data/ext/cumo/narray/gen/def/sfloat.rb +2 -3
  52. data/ext/cumo/narray/gen/def/uint16.rb +1 -3
  53. data/ext/cumo/narray/gen/def/uint32.rb +1 -3
  54. data/ext/cumo/narray/gen/def/uint64.rb +1 -3
  55. data/ext/cumo/narray/gen/def/uint8.rb +1 -3
  56. data/ext/cumo/narray/gen/narray_def.rb +42 -1
  57. data/ext/cumo/narray/gen/spec.rb +5 -0
  58. data/ext/cumo/narray/gen/tmpl/accum_binary.c +63 -5
  59. data/ext/cumo/narray/gen/tmpl/accum_binary_kernel.cu +21 -5
  60. data/ext/cumo/narray/gen/tmpl/aref_cpu.c +8 -0
  61. data/ext/cumo/narray/gen/tmpl/batch_norm.c +1 -1
  62. data/ext/cumo/narray/gen/tmpl/batch_norm_backward.c +2 -2
  63. data/ext/cumo/narray/gen/tmpl/binary.c +3 -9
  64. data/ext/cumo/narray/gen/tmpl/binary2_kernel.cu +2 -2
  65. data/ext/cumo/narray/gen/tmpl/binary_kernel.cu +4 -4
  66. data/ext/cumo/narray/gen/tmpl/binary_s_kernel.cu +2 -2
  67. data/ext/cumo/narray/gen/tmpl/bincount_kernel.cu +4 -4
  68. data/ext/cumo/narray/gen/tmpl/clip_kernel.cu +6 -6
  69. data/ext/cumo/narray/gen/tmpl/cond_binary_kernel.cu +2 -2
  70. data/ext/cumo/narray/gen/tmpl/cond_unary_kernel.cu +2 -2
  71. data/ext/cumo/narray/gen/tmpl/conv.c +5 -4
  72. data/ext/cumo/narray/gen/tmpl/conv_grad_w.c +5 -4
  73. data/ext/cumo/narray/gen/tmpl/conv_transpose.c +5 -4
  74. data/ext/cumo/narray/gen/tmpl/cum.c +88 -10
  75. data/ext/cumo/narray/gen/tmpl/cum_kernel.cu +179 -17
  76. data/ext/cumo/narray/gen/tmpl/each.c +4 -3
  77. data/ext/cumo/narray/gen/tmpl/each_with_index.c +4 -3
  78. data/ext/cumo/narray/gen/tmpl/ewcomp_kernel.cu +2 -2
  79. data/ext/cumo/narray/gen/tmpl/extract_cpu.c +8 -0
  80. data/ext/cumo/narray/gen/tmpl/extract_data.c +8 -0
  81. data/ext/cumo/narray/gen/tmpl/eye_kernel.cu +1 -1
  82. data/ext/cumo/narray/gen/tmpl/fill_kernel.cu +2 -2
  83. data/ext/cumo/narray/gen/tmpl/fixed_batch_norm.c +1 -1
  84. data/ext/cumo/narray/gen/tmpl/format.c +2 -2
  85. data/ext/cumo/narray/gen/tmpl/format_to_a.c +2 -2
  86. data/ext/cumo/narray/gen/tmpl/frexp_kernel.cu +2 -2
  87. data/ext/cumo/narray/gen/tmpl/gemm.c +21 -34
  88. data/ext/cumo/narray/gen/tmpl/inspect.c +0 -1
  89. data/ext/cumo/narray/gen/tmpl/layer_norm.c +8 -61
  90. data/ext/cumo/narray/gen/tmpl/logseq_kernel.cu +2 -2
  91. data/ext/cumo/narray/gen/tmpl/map_with_index.c +5 -1
  92. data/ext/cumo/narray/gen/tmpl/median.c +9 -4
  93. data/ext/cumo/narray/gen/tmpl/new_dim0_kernel.cu +1 -1
  94. data/ext/cumo/narray/gen/tmpl/poly_kernel.cu +3 -3
  95. data/ext/cumo/narray/gen/tmpl/pooling_backward.c +1 -1
  96. data/ext/cumo/narray/gen/tmpl/pooling_forward.c +1 -1
  97. data/ext/cumo/narray/gen/tmpl/pow_kernel.cu +4 -4
  98. data/ext/cumo/narray/gen/tmpl/quantize_symmetric.c +74 -0
  99. data/ext/cumo/narray/gen/tmpl/quantize_symmetric_kernel.cu +67 -0
  100. data/ext/cumo/narray/gen/tmpl/rand.c +16 -15
  101. data/ext/cumo/narray/gen/tmpl/rand_kernel.cu +19 -24
  102. data/ext/cumo/narray/gen/tmpl/rand_norm.c +7 -25
  103. data/ext/cumo/narray/gen/tmpl/rand_norm_kernel.cu +19 -24
  104. data/ext/cumo/narray/gen/tmpl/rms_norm.c +38 -0
  105. data/ext/cumo/narray/gen/tmpl/rms_norm_kernel.cu +57 -0
  106. data/ext/cumo/narray/gen/tmpl/seq_kernel.cu +2 -2
  107. data/ext/cumo/narray/gen/tmpl/set2_kernel.cu +2 -2
  108. data/ext/cumo/narray/gen/tmpl/softmax.c +4 -39
  109. data/ext/cumo/narray/gen/tmpl/softmax_kernel.cu +5 -5
  110. data/ext/cumo/narray/gen/tmpl/sort.c +7 -4
  111. data/ext/cumo/narray/gen/tmpl/store.c +8 -0
  112. data/ext/cumo/narray/gen/tmpl/store_array.c +3 -2
  113. data/ext/cumo/narray/gen/tmpl/store_array_kernel.cu +4 -4
  114. data/ext/cumo/narray/gen/tmpl/store_bit_kernel.cu +2 -2
  115. data/ext/cumo/narray/gen/tmpl/store_from.c +1 -16
  116. data/ext/cumo/narray/gen/tmpl/store_from_kernel.cu +4 -4
  117. data/ext/cumo/narray/gen/tmpl/to_a.c +1 -2
  118. data/ext/cumo/narray/gen/tmpl/unary2_kernel.cu +2 -2
  119. data/ext/cumo/narray/gen/tmpl/unary_kernel.cu +2 -2
  120. data/ext/cumo/narray/gen/tmpl/unary_ret2_kernel.cu +2 -2
  121. data/ext/cumo/narray/gen/tmpl/unary_s_kernel.cu +2 -2
  122. data/ext/cumo/narray/gen/tmpl_bit/aref_cpu.c +3 -2
  123. data/ext/cumo/narray/gen/tmpl_bit/binary_kernel.cu +4 -4
  124. data/ext/cumo/narray/gen/tmpl_bit/bit_count.c +6 -5
  125. data/ext/cumo/narray/gen/tmpl_bit/bit_reduce.c +23 -21
  126. data/ext/cumo/narray/gen/tmpl_bit/each.c +4 -3
  127. data/ext/cumo/narray/gen/tmpl_bit/each_with_index.c +4 -3
  128. data/ext/cumo/narray/gen/tmpl_bit/extract_cpu.c +1 -2
  129. data/ext/cumo/narray/gen/tmpl_bit/fill_kernel.cu +3 -3
  130. data/ext/cumo/narray/gen/tmpl_bit/format.c +2 -3
  131. data/ext/cumo/narray/gen/tmpl_bit/format_to_a.c +2 -3
  132. data/ext/cumo/narray/gen/tmpl_bit/inspect.c +1 -2
  133. data/ext/cumo/narray/gen/tmpl_bit/mask_kernel.cu +3 -3
  134. data/ext/cumo/narray/gen/tmpl_bit/store_array.c +3 -2
  135. data/ext/cumo/narray/gen/tmpl_bit/store_array_kernel.cu +5 -5
  136. data/ext/cumo/narray/gen/tmpl_bit/store_bit_kernel.cu +4 -4
  137. data/ext/cumo/narray/gen/tmpl_bit/store_from_kernel.cu +2 -2
  138. data/ext/cumo/narray/gen/tmpl_bit/to_a.c +2 -3
  139. data/ext/cumo/narray/gen/tmpl_bit/unary.c +6 -0
  140. data/ext/cumo/narray/gen/tmpl_bit/unary_kernel.cu +4 -4
  141. data/ext/cumo/narray/gen/tmpl_bit/where.c +2 -2
  142. data/ext/cumo/narray/gen/tmpl_bit/where_kernel.cu +4 -4
  143. data/ext/cumo/narray/index.c +35 -25
  144. data/ext/cumo/narray/index_kernel.cu +39 -12
  145. data/ext/cumo/narray/math.c +38 -8
  146. data/ext/cumo/narray/narray.c +188 -55
  147. data/ext/cumo/narray/ndloop.c +459 -24
  148. data/ext/cumo/narray/ndloop_kernel.cu +18 -18
  149. data/ext/cumo/narray/sort_kernel.cu +88 -40
  150. data/lib/cumo/cuda/compiler.rb +83 -30
  151. data/lib/cumo/cuda/device.rb +8 -1
  152. data/lib/cumo/cuda/elementwise_kernel.rb +131 -0
  153. data/lib/cumo/cuda/event.rb +68 -0
  154. data/lib/cumo/cuda/function.rb +45 -0
  155. data/lib/cumo/cuda/module.rb +30 -3
  156. data/lib/cumo/cuda/nvrtc_program.rb +16 -1
  157. data/lib/cumo/cuda/pinned_memory.rb +93 -0
  158. data/lib/cumo/cuda/reduction_kernel.rb +279 -0
  159. data/lib/cumo/cuda/stream.rb +114 -0
  160. data/lib/cumo/cuda/user_kernel.rb +169 -0
  161. data/lib/cumo/cuda.rb +6 -0
  162. data/lib/cumo/narray/extra.rb +30 -1
  163. data/test/bit_test.rb +279 -15
  164. data/test/cuda/compiler_test.rb +84 -0
  165. data/test/cuda/device_test.rb +42 -0
  166. data/test/cuda/elementwise_kernel_test.rb +184 -0
  167. data/test/cuda/function_test.rb +162 -0
  168. data/test/cuda/module_test.rb +15 -0
  169. data/test/cuda/name_expression_test.rb +172 -0
  170. data/test/cuda/pinned_memory_test.rb +138 -0
  171. data/test/cuda/reduction_kernel_test.rb +189 -0
  172. data/test/cuda/runtime_test.rb +21 -0
  173. data/test/cuda/stream_test.rb +246 -0
  174. data/test/cudnn_test.rb +54 -0
  175. data/test/cumo_test.rb +47 -0
  176. data/test/fused_test.rb +300 -24
  177. data/test/math_test.rb +267 -0
  178. data/test/narray_alt_coverage_test.rb +117 -1
  179. data/test/narray_test.rb +851 -12
  180. metadata +34 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 695bd66c93bd2511f905d74cf242a7d2d7d0ac4aea8f5915fb722e013e658e9a
4
- data.tar.gz: fa51db7ef5f01745ded6d71834bde62fa238fb92cba9bb358374293a18b7577b
3
+ metadata.gz: 78444d61470bd2cf534dfadba1facef1d92785cf85db498ca14d893242532591
4
+ data.tar.gz: 7bb662f78bd6e130c5a93db7fe6ac44fc2dad14f58f3788de48c04f7830197b4
5
5
  SHA512:
6
- metadata.gz: fbad56229183188dc36b2341105a68a5ca670de3a86ac9d50887544bebf4200e80c0f571783aefe24657c609aa86bc3f6af0da8d5943b56c0c91031ea7c22244
7
- data.tar.gz: a5a3e9cfa2e99873b3e7f555fec3e397ed8c9814fbd0255ccc2e0cd52ac9d337fd654741a00412f1b0eb46a777ed346fa46ae98c7e4bf45ce0d7eb8de964fa75
6
+ metadata.gz: 46d001da3c8c82e4f835fe6643cec1a1f5a4b28864283286a52f98887d975673914ee475ecd90d97042e5c16e45c525060d595ecd8301a427fed5060a8efc7b0
7
+ data.tar.gz: ae0052bea91487469a2dd6052c08f9bb8c74799447ea165d189babc88ccd94b7c686e50d1ff69f1df75ec2d20986c7113ff2d3b949205c4ac72132f7b8d468af
data/CHANGELOG.md CHANGED
@@ -1,3 +1,88 @@
1
+ # 0.10.0 (2026/09/22)
2
+
3
+ Breaking changes:
4
+
5
+ * `CUMO_COMPATIBLE_MODE`, `CUMO_SHOW_WARNING`, `CUMO_SHOW_WARNING_ONCE` and `CUMO_MEMORY_POOL` read `off`, `false` and `no` as a no, in any case, where anything but `OFF`, `0` and `NO` used to turn the flag on without a word. A value they do not recognise keeps the flag's default and warns, and an empty value reads as unset (PR #514)
6
+ * An empty array keeps its shape through everything that goes through ndloop: `Cumo::Int32.new(0, 3).copy` and `(a + 1)` answer `[0, 3]` where they answered `[0, 0]`. `to_a` nests down to the first zero and no further, the way numpy's `tolist` does, so `(3, 0)` is `[[], [], []]` and `(0, 3, 4)` is `[]` (PR #513)
7
+ * `Marshal.dump` of a subclass of `Cumo::RObject` writes its objects, where it wrote the raw addresses the buffer holds. Data the old dump wrote for such a subclass no longer loads, and what it held was addresses from a process that has since exited (PR #509)
8
+
9
+ Fixes:
10
+
11
+ * Fix `all?(axis: [])` answering a zero-dimensional `Cumo::Bit`, which is truthy whatever it holds, for an array it is false for. Whether an axis was named is now read off the reduction rather than off the list (PR #512)
12
+ * Fix `all?`, `any?` and `none?` answering a zero-dimensional `Cumo::Bit` when a default keyword such as `axis: nil` or `keepdims: false` is written out, so `Cumo::Bit[1, 1, 0].all?(axis: nil)` read as true (PR #510)
13
+ * Fix an axis reduction of an empty `Cumo::Bit` answering an empty array: `Cumo::Bit.new(0, 3).all?(axis: 0)` answers `[1, 1, 1]` and `count_true(axis: 0)` answers `[0, 0, 0]` (PR #508)
14
+ * Fix the five `Cumo::Bit` reductions answering false, or an Integer, for an empty array before reading their arguments. `Cumo::Bit[].all?` is true and `any?` false, as Ruby's are, `count_true` answers a `UInt64`, and an axis that is not there raises `DimensionError` (PR #507)
15
+ * Fix `CastError` for a subclass of a dtype, which broke `store`, arithmetic and the reductions, and put an element of one into an RObject array as one object repeated (PR #506)
16
+ * Fix `Device#with` running its block on the device it was already on, whichever device it was given (PR #499)
17
+ * Fix `reshape` writing the new shape over whatever the receiver's `dup` answered, which could leave an array claiming a size its buffer cannot hold. The answer `dup` gives has to be the same class, own its data and not be frozen (PR #494)
18
+ * Fix `gemm` walking a copy of a non-contiguous operand with the original's dimensions when `dup` answered something else (PR #493)
19
+ * Fix `marshal_dump` and `to_binary` handing out memory past the end of the array when a view's `dup` answered another shape, class or layout (PR #492)
20
+ * Fix `median` and `sort` ignoring the axes a view marked with `:sum`, so `f[:sum, true].median` answered the whole array. `median` keeps its receiver rather than standing its copy in its place, and no longer copies an operand it only reads, so the pool holds less afterwards (PR #489, PR #488, PR #487)
21
+ * Fix `Cumo::Bit#copy` answering its own receiver when the receiver is inplace, which let a write to the copy reach the original and gave every reduction that copies an index-backed view first the wrong rows (PR #486)
22
+ * Fix the interpreter abort when `all?` or `any?` reduces a `[:sum, true]` view (PR #485)
23
+
24
+ Changes:
25
+
26
+ * `compile_with_cache` answers the same module for the same source, options and device for the rest of the process, so it can be called wherever the kernel is launched: a repeated call costs 0.0009 ms where it read the cubin back and loaded a new module for 0.033 ms, nine times the launch itself. A module nobody holds any more is unloaded when Ruby collects it, and `Cumo::CUDA::Compiler.clear_modules` makes the next call read the disk cache or compile again (PR #531)
27
+ * The ports of the CuPy examples live in `examples/`, one Ruby file per original, with `backend.rb` running the CPU side on Numo and the GPU side on Cumo under `GPU=1`. The gem leaves them out (PR #530)
28
+ * Drop a dead first definition of `Compiler#save_cache` that `ruby -W` warned about (PR #529)
29
+ * Reading a result back from the device copies the range it needs into pinned host memory rather than reading managed memory in place, which faulted the page over and back once per read where a small block shares its page with others: `Float(r.sum)` takes 0.012 ms where it took 0.885, `to_a` of eight elements 0.016 ms where it took 0.97, and `to_binary` of 8 MB 2.3 ms where it took 7.6. `to_a`, `each`, `each_with_index`, `map_with_index`, `inspect`, `format`, `extract_cpu`, `aref_cpu`, `to_binary` and the RObject marshal all take the copy, and a block that writes the array it is walking still sees what it wrote (PR #528)
30
+ * `rand` and `rand_norm` fill an array whose last axis has length one in one kernel launch rather than one per row: a `[1048576, 1]` column takes 0.018 ms where it took 1799. A seed produces the same values as before whatever the layout (PR #527)
31
+ * A module answers a template kernel such as `kernel<float>` by its name expression: `NVRTCProgram.new` and `compile_with_cache` take `name_expressions:`, the mangled names are cached beside the cubin, and `Module#get_function` looks an expression up before asking the driver. Arrays of structs are arrays whose trailing axes are the fields, and a struct by value is a packed String. The cubin cache had been writing empty files where `/tmp` and the cache directory are different file systems, so every `compile_with_cache` compiled again; it now writes next to its place (PR #526)
32
+ * Add `Cumo::CUDA::PinnedMemory`, a page-locked host buffer read and written as bytes, and `NArray#set` and `NArray#get`, which copy through it asynchronously on the current stream or on `stream:` the way `cupy.ndarray.set` and `get` do. A copy in flight keeps both ends until it is done (PR #525)
33
+ * Add `Cumo::CUDA::Stream` and `Cumo::CUDA::Event`, after `cupy.cuda`: `Stream.new`, `with`, `use`, `synchronize`, `record`, `wait_event`, `Stream.null`, `Stream.current`, `Event.new`, `record`, `synchronize`, `done?` and `Cumo::CUDA.get_elapsed_time`, and `Function#launch` takes `stream:`. `Stream#with` orders the block after what the previous stream had queued and waits for the stream on the way out (PR #524)
34
+ * Every kernel, copy, cuBLAS and cuDNN call, thrust and CUB call goes to the current stream of the thread, which is the null stream until a `Stream` is used, so a stream a caller picks reaches all of it. `Runtime.cudaStreamCreateWithFlags`, `cudaStreamDestroy`, `cudaStreamSynchronize` and `current_stream=` are bound (PR #523)
35
+ * Add `Runtime.cudaDeviceCanAccessPeer` and `Device#can_access_peer?`. A CUDA error raised for a device that is not there no longer stays as the runtime's last error for the next kernel launch to read as its own (PR #521)
36
+ * The CI ccache ceiling is 2 GB, since one build no longer fit in 1 GB and the CUDA 12 jobs compiled again what had been evicted (PR #522)
37
+ * Add `Cumo::CUDA::ReductionKernel`, a reduction written as a map, a reduce and a post expression with an identity, called with `axis:` and `keepdims:`, after CuPy's. This is the last half of #106 (PR #520)
38
+ * Add `Cumo::CUDA::ElementwiseKernel`, a kernel written as one expression over a parameter list, with one-letter types resolved from the arguments and broadcasting, compiled once per set of types, after CuPy's. Bit, half precision and complex arguments are refused for now (PR #519)
39
+ * A kernel compiled with NVRTC can be launched: `Module#get_function` answers a `Cumo::CUDA::Function` whose `launch` takes the arguments, the grid, the block and the dynamic shared memory. An NArray hands over its device pointer, an Integer goes as a `long long`, a Float as a `double`, and a String as its bytes. On CUDA 12.4 or later the count and the sizes of the arguments are checked against the kernel before the launch (PR #518, PR #500)
40
+ * `CUMO_ALLOW_TF32` puts single precision `gemm` on the tensor cores as well as convolutions: a `[4096, 4096]` SFloat gemm goes from 17.8 to 28.4 TFLOP/s, and its answer moves from 4e-07 to 3e-04 of a double precision reference. Off, which is the default, answers bit for bit what it did. The flag is read in `cumo.c` as `Cumo.allow_tf32?` rather than in `cudnn.c`, so a build without cuDNN has it too (PR #517, PR #516)
41
+ * Every dtype's `gemm` goes through `cublasGemmStridedBatchedEx`, the entry point that can name a compute type. The answers are the same bytes, and no change in speed could be told apart from the noise (PR #515)
42
+ * `all?`, `any?` and `none?` on an empty `Cumo::Bit` with no axis named answer the identity without reserving anything or waiting on the device: 0.045 us a call where it took 12.3 and 512 bytes (PR #511)
43
+ * `sort_index`, and `sort` and `median` over a view, address by rank rather than through the run-time accessor: `sort_index` over a 4096 by 1024 SFloat takes 2.2 ms where it took 5.6, and an inplace `sort` over a column slice 2.0 ms where it took 6.5 (PR #505)
44
+ * `cumsum` and `cumprod` along an axis send every row to one scan keyed on its row rather than starting a scan per row, and a view no single stride reaches is gathered by a kernel that addresses by rank: over a `[512, 2048]` SFloat a column slice takes 25.7 us where 0.9.0 took 50.7, and a transposed view 45.2 us where it took 308.3. A reversed view is read through its stride and costs what a flat one does (PR #504, PR #503, PR #498, PR #496)
45
+ * The default cuDNN workspace ceiling is 128 MB rather than 8 MB, which reaches the faster convolution algorithms: the twenty convolutions of a ResNet-18 forward pass at batch 16 take 5.23 ms where they took 9.80, and a half precision convolution reaches its tensor core algorithms. The search reserves the whole ceiling while it runs, so the peak grows with it and goes back to the pool afterwards (PR #502)
46
+ * A single precision convolution stays off the tensor cores unless `CUMO_ALLOW_TF32` says yes, where cuDNN's default let the algorithm search move it there and round the operands to a 10 bit significand (PR #501)
47
+ * Say in the README that `contiguous?` answers true for a view whose length-one axis Numo counts as breaking contiguity, that copying a transposed operand beats handing it to cuBLAS as it is for most shapes, what the memory numbers of the pool and of `nvidia-smi` mean, where a bfloat16 integer index goes past 256, and that `quantize_symmetric` breaks ties away from zero where numpy and torch round to even (PR #497, PR #495, PR #491, PR #484)
48
+ * Say what routing `silu` through `sigmoid` would change, rather than a fraction that no dtype matches (PR #483)
49
+ * Add `Cumo::NMath.sigmoid`, which keeps a value where `1 / (1 + exp(-x))` written out gives out: single reaches zero at -104 rather than -89, double at -745 rather than -710 (PR #482)
50
+ * Add `Cumo::NMath.softplus`, which does not overflow where `log(1 + exp(x))` written out does, and answers `x` to the last bit past 11.09 in half and 88.7 in single (PR #481)
51
+
52
+ # 0.9.0 (2026/09/19)
53
+
54
+ Breaking changes:
55
+
56
+ * An integer array divided by a zero Ruby number raises `ZeroDivisionError` before the kernel runs rather than after it, so `a.inplace / 0` leaves `a` alone where it wrote over it first. The divisor was on the host all along, so the flag the device raised and the `cudaDeviceSynchronize` that read it back are gone: `Cumo::Int32.new(1, 768)` costs 2.34us for `a / 2` where it cost 9.27, and 2.20 for `a % 2` where it cost 7.88. `2 / a` and an array by an array keep the flag, the divisor really being on the device there (PR #466)
57
+
58
+ Fixes:
59
+
60
+ * Fix `mulsum` reducing everything where the receiver is a view that marked its axes with `:sum`, `:reduce` or `:+` and the operands have different dtypes. The marking lives on the array the method is asked of, and `mulsum` answers another class by putting a different array in the receiver's place, so `Cumo::Int8.new(4, 6)[:sum, true].mulsum(a_float)` answered a scalar where the same call with matching dtypes answers a row of six. numo answers the same wrong thing (PR #480)
61
+ * Fix an elementwise operation whose inplace receiver shares memory with an operand that walks it differently, `a.inplace + a.transpose` answering something new on every run: the element a thread reads is one another thread is writing, and nothing orders the two on a device. An operand sitting exactly where the output does is left alone, which is the common `a.inplace + a` (PR #473)
62
+ * Fix `to_binary` handing out whatever lay past the end of a `Cumo::Bit` array whose size is not a multiple of eight. `Cumo::Bit.new(24).fill(1)[0..3].to_binary` answered `[255]` where four bits belong to it, and a view copied first answered the memory pool's contents, so the same expression could answer differently from one run to the next. `marshal_dump` goes through here too (PR #471)
63
+ * Fix an inplace view built from an index array reducing from the wrong rows. The copy that makes such a view contiguous carried `CUMO_NDF_INPLACE`, so for an inplace receiver it answered self and the reduction walked the index-backed view it meant to have copied. `median` and `max_index` answered their own wrong rows the same way, and inplace survives an operation, so `(v.inplace * 2).sum(axis: 2)` reached it too (PR #461)
64
+ * Hold `layer_norm`, `rms_norm` and `softmax` to the same checks. `softmax` measured the result by its element count where the other two compare the shape, so an `allocate` that reshaped either side to the same count left every check true: reshaping the result answered the right values under the wrong shape, and reshaping the receiver normalized along the wrong axis (PR #459)
65
+
66
+ Changes:
67
+
68
+ * Add `rms_norm`, which normalizes a row in one kernel rather than the six it is written out of. This is `layer_norm` without the centring, the layer Llama and the models after it normalize with, so it takes no beta. On SFloat `[1, 768]` it goes from 14.2 to 2.9us and `[4096, 768]` from 122.5 to 38.5. No shape is slower (PR #458)
69
+ * Add `silu` to `Cumo::NMath`, which takes one kernel rather than the five it is written out of: 768 elements go from 13.4 to 3.6us. It follows `torch.nn.functional.silu`, answering within one unit in the last place of the true value at the nine double points measured, and matching it at the edges (PR #460)
70
+ * Add `quantize_symmetric`, which quantizes a row to `Cumo::Int8` in one kernel rather than the six it is written out of and hands the scale back. A launch costs about two microseconds whatever it is handed, so a short row pays for those rather than for its bytes (PR #476)
71
+ * Speed up `mulsum`. Operands of different dtypes are read where they already are and converted one element at a time rather than cast into an array of the wider type first, so `SFloat#mulsum(Int8)` at `[4096, 4096]` goes from 384 to 53us. A broadcast operand no longer falls off the fast path, `1000x12x64` against `1000x12x1` over axis 0 going from 13.5 to 4.9us, and the thread layout takes the shorter step of the two operands rather than the first one's, so `DFloat[64,1].mulsum(DFloat[64,1024], axis: 1)` goes from 16.26 to 5.68 whichever order it is written in (PR #479, PR #477, PR #475)
72
+ * Fix every allocation slowing down as the memory pool fragments, the arena having been rebuilt whenever an allocation emptied a bin. GPT-2 124M generating 256 tokens goes from 11.9 to 3.2 seconds in fp16, where the pool ends up with 4035 free chunks against fp32's 71 (PR #474)
73
+ * Range check an NArray index on the device rather than copying the indices to the host and back. `Cumo::SFloat.new(50257, 768)`, `a[idx, true]`: 4096 indices go from 58.2 to 22.2us and 64 from 20.8 to 13.8. Nothing reads managed memory from the host on that path any more, which is what breaks under Ractor (sonots/cumo#180) (PR #470)
74
+ * Copy a transposed view through the shared-memory tile the typed store has used since 0.7.0. `Cumo::SFloat.new(4096, 1024)`: `a.transpose.copy` goes from 338 to 97us, level with `a.transpose.dup` at 102, and `a.transpose.max_index(axis: 1)` from 362 to 107. Every reduction that needs a contiguous operand goes through this copy (PR #468)
75
+ * Reduce a view built from an index array without staging it in a buffer first. Copying a `[6250, 64]` view of a `[100000, 64]` array goes from 13.9 to 7.2us and reducing it from 25.1 to 18.3 (PR #462)
76
+ * Let `store_binary` reach a view it could only read back from. `to_binary` reads any view, so `v.store_binary(v.to_binary)` raised for the same `v` that had just produced the string. A view the bytes cannot move into directly now takes them through an array of its own class and shape (PR #472)
77
+ * Answer `respond_to?` for the methods `Cumo::NMath` dispatches. Every one of them arrives through `method_missing`, so `respond_to?` answered false for `sqrt` and `exp` alike, which code that checks for a feature before calling it reads as absence (PR #463)
78
+ * Take one Ruby method dispatch out of every ndloop call, the argument list always being a plain Array where `rb_obj_dup` took the general path: about 800 fewer retired instructions per operation, 3.4% of the 23,200 an operation takes (PR #465)
79
+ * Tell which array a type check rejected and what it wanted. It said "invalid NArray type (class)", naming neither, so two checks side by side read exactly alike; it now says "gamma must be Cumo::SFloat, not Cumo::HFloat" (PR #478)
80
+ * Say in the README that `reshape` answers a copy where slicing answers a view, priced across four shapes: on a GPU it is an allocation and a copy kernel every call, 58.1us and 12MB for a `4096x768` SFloat against 0.22us and nothing for `reshape!` (PR #457)
81
+ * Show the changelog, source and issue links on the gem page, and require multi-factor authentication to push the gem (PR #456)
82
+ * Share one body between the half and bfloat16 macros so a fix reaches both, 399 duplicated lines where it would have reached one. The compiled code is unchanged: the device SASS is identical (PR #464)
83
+ * Keep the index-array test in one place so copy and store cannot drift apart, 172 generated copies of it becoming one (PR #467)
84
+ * Drop the `Cumo::NMath::DISPATCH` rows no argument can reach. A bare Ruby numeric takes an NArray row, so the `Integer`, `Float` and `Complex` ones were never read: 944 calls over 16 kinds of argument reached them zero times (PR #469)
85
+
1
86
  # 0.8.0 (2026/09/16)
2
87
 
3
88
  Breaking changes: