cumo 0.14.0 → 0.15.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f09e6c09816ea16f1a27425e44170c7ea191b771f0a3b26b2b7298a1e53b6e6c
4
- data.tar.gz: f4739b41e92d5f5920ea694393f3ab71241e9b428a29416db71e91d5fcb0f13e
3
+ metadata.gz: d6fcb0462dc2821749f43c243267e8eef32c5362d2a647769c3f93ccc048ddd0
4
+ data.tar.gz: 131ec1045f331591fb3f1c522824ea1e14c56fad40091bccacde73bb7f64a649
5
5
  SHA512:
6
- metadata.gz: d58332736efabf28caeec996882ceb99435885e41d017a40911c6a85346a4f03428e32cb7cae32e8dae9633e47f7497dc35075309ec32d61d607504ef5f61fb1
7
- data.tar.gz: d276cc4b4d19203ab34bfae56e96dd6e3220a780f07d3da8934e9c9241336fcb31c2040e013a7e461b47ec93461dcff21ca126597742c184e52221c0112bf196
6
+ metadata.gz: dfbb78f6f9ea625ae2848210722aa58106e6efd80af31d38e239585def253b7fb0e00426d18804e4177040492469d3498eaf61dca0cb7880ee482bd1c9669d0e
7
+ data.tar.gz: 80c944b3b2a544c593a221c9600c2a8cc441b273e37dc77f3ca1bbb738e381e1fcc0db42cbc66265f6d75832ee0ca396b008ffec7ff666549db313d386b82e81
data/CHANGELOG.md CHANGED
@@ -1,8 +1,42 @@
1
+ # 0.15.0 (2026/11/02)
2
+
3
+ Breaking changes:
4
+
5
+ * A subscript of an empty array is read as it is for any other array, where every subscript of an empty array raised `Cumo::NArray::ShapeError`. `a[true, 1]` of a 0 x 3 array answers an empty view, and a subscript that names an element raises as it does for any array, so `a[0]` raises `IndexError`. An empty range now selects nothing when it starts where an element could be inserted, as with `Array#[]`, so `b[2...2, true]` of a 2 x 3 array answers a 0 x 3 view where it raised `RangeError`. The README lists where this differs from Numo (PR #641)
6
+ * `Cumo::RObject` no longer has `store_binary`, `to_string`, `hton` or `Cumo::RObject.from_string`, and `set` with a String raises `NoMethodError` for it. They read or wrote the object references of an RObject as raw bytes, so the next garbage collection could crash, and `to_string` gave out heap addresses. Calling the methods of `Cumo::NArray` on an RObject through `bind_call` raises `TypeError` (PR #638)
7
+
8
+ Fixes:
9
+
10
+ * Fix `require "cumo"` under `ruby -w` warning "method redefined; discarding old %". Cumo no longer defines `Range#%` again, which Ruby has had since 2.6 and which works the same (PR #643)
11
+ * Fix a crash, possible since 0.7.0, when storing an Array whose NArray row of another type is followed by a row that raises, such as one with the wrong number of dimensions. The garbage collector could free the cast copy of the row before the store let go of it (PR #642)
12
+ * Fix a range with a negative step and an excluded or missing end selecting the wrong elements: `(4...1).step(-1)` selected 5 elements of a 5-element array where it should select 3, and `(3..).step(-1)` none. Fix a stepped range that selects nothing returning elements, as `(1..0).step(2)` returned one and `(3...3).step(-1)` two (PR #641)
13
+ * Fix `to_a` and `format_to_a` aborting when Cumo is compiled with `RUBY_DEBUG` against a Ruby whose headers check the index of `RARRAY_AREF`, as Ruby master does since ruby/ruby@61261bf5c4 (PR #640)
14
+
15
+ Changes:
16
+
17
+ * CI builds against CUDA 13.4.3 on Ubuntu 26.04 where it built against 13.4.1 on Ubuntu 24.04, and the CUDA 12.9.2 job builds for sm_121 where it built for sm_120 (PR #639)
18
+ * The first `sort`, `median` or `sort_index` of a type loads the sort kernels of that type alone, where the first of a process loaded those of all twelve types. On SFloat the first `sort` is about 5.7 times faster, `median` about 9 times and `sort_index` about 3 times (PR #636)
19
+ * The first `cumsum` or `cumprod` of a process no longer loads every sort kernel, which makes the first SFloat `cumsum` about 2.1 times faster and keeps the peak RSS 57 MB lower (PR #635)
20
+ * `sum`, `mean`, `max`, `min` and the `dot` of two vectors have a kernel module of their own, so the first of them on a type no longer loads every reduction of the type. The first SFloat `sum` or vector `dot` of a process is about 8.6 times faster (PR #634)
21
+
22
+ # 0.14.1 (2026/10/22)
23
+
24
+ Fixes:
25
+
26
+ * Fix a host loop such as `each` yielding the old values of an array after its block wrote the array through `store_binary`, an in-place `cumsum` or `cumprod`, or an in-place `map` or `map_with_index`, also one paused by an Enumerator (PR #631, PR #630)
27
+ * Fix an in-place `map_with_index` over a view that reaches an element more than once, such as `x[[0, 0, 0]]`, reading the element as it was before its own write. It now reads its own write, as `map` and numo do (PR #631)
28
+ * Fix the release gem recording the tag of the previous release as `Cumo::REVISION`, as the 0.14.0 gem does with `"v0.13.0-11-gafd8ead9"`. `rake release` now makes the tag before it builds the gem, so this gem gives `"v0.14.1"` (PR #626)
29
+
30
+ Changes:
31
+
32
+ * `+`, `-`, `*` and `/` between a complex array and a real array of the same precision, in either order, read the real array as it is rather than casting it to a complex array first. On 4M elements this makes SComplex with SFloat about 1.7 to 1.8 times faster, and DComplex divided by DFloat about 1.4 times. The answers are the same but for the sign of a NaN (PR #632)
33
+ * A host loop such as `each` whose block writes to the device copies the rest of the row, or of the array, again at once when copying the elements one at a time would cost more. With one write early in an `each_with_index` over 100,000 elements, the loop is about 90 times faster and about as fast as with no write. With a write at every 100th element it is about 12 times faster, and with a write at every other element about 16% slower (PR #631)
34
+
1
35
  # 0.14.0 (2026/10/18)
2
36
 
3
37
  Breaking changes:
4
38
 
5
- * `cumo.h` no longer defines `CUMO_VERSION` or `CUMO_VERSION_CODE`. `Cumo::VERSION` and `Cumo::NArray::VERSION` are defined in Ruby, and the new `Cumo::REVISION` tells a release gem from one built from a checkout: it gives `"v0.14.0"` for this release and something like `"v0.14.0-3-g1a2b3c4"` for a gem built at a later commit (PR #617)
39
+ * `cumo.h` no longer defines `CUMO_VERSION` or `CUMO_VERSION_CODE`. `Cumo::VERSION` and `Cumo::NArray::VERSION` are defined in Ruby. The new `Cumo::REVISION` tells a release gem from one built from a checkout. A release gives its tag, and a gem built at a later commit gives something like `"v0.14.0-3-g1a2b3c4d"`. The 0.14.0 gem was built before its tag was made, so it gives `"v0.13.0-11-gafd8ead9"`. `rake release` makes the tag before the gem from the next release on (PR #617, PR #626)
6
40
 
7
41
  Fixes:
8
42
 
data/README.md CHANGED
@@ -198,6 +198,30 @@ Cumo::DFloat.new(1, 3).seq.transpose.contiguous? #=> true
198
198
  Code that runs against both libraries should not read `contiguous?` and expect the same answer.
199
199
  Where it wants a contiguous array it can ask for one, since `dup` answers one in either library.
200
200
 
201
+ #### Subscripts That Select Nothing
202
+
203
+ Numo raises `ShapeError` for every subscript of an empty array.
204
+ Cumo returns the empty view numpy does, and raises only for a subscript that names an element.
205
+
206
+ ```ruby
207
+ a = Cumo::DFloat.new(0, 3)
208
+ a[true, 1].shape #=> [0], where Numo raises ShapeError
209
+ a[0] # raises IndexError, where Numo raises ShapeError
210
+ ```
211
+
212
+ A range that selects nothing only has to start where an element could be inserted, as with `Array#[]`.
213
+ Numo raises `RangeError` when such a range starts at either end of an axis.
214
+
215
+ ```ruby
216
+ b = Cumo::DFloat.new(2, 3).seq
217
+ b[2...2, true].shape #=> [0, 3], where Numo raises RangeError
218
+ b[3...3, true] # raises RangeError in both
219
+ ```
220
+
221
+ A stepped range that selects nothing also selects nothing in Cumo.
222
+ Numo returns one element for `(1..0).step(2)` and two for `(3...3).step(-1)`.
223
+ `compatible_mode` does not change any of this.
224
+
201
225
  ### Keeping Scalars On The Device
202
226
 
203
227
  The 0-dimensional return is what lets an iterative loop stay on the GPU.
data/Rakefile CHANGED
@@ -2,6 +2,35 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rake/testtask"
5
+ require "rubygems/package"
6
+
7
+ Rake::Task["release"].clear
8
+ desc "Tag the version, build the gem at the tag, and push the branch, the tag and the gem"
9
+ task :release, [:remote] do |_, args|
10
+ git = ->(*argv) { IO.popen(["git", *argv], err: File::NULL, &:read).strip }
11
+ version = File.read(File.join(__dir__, "lib/cumo/version.rb"))[/VERSION = "([^"]+)"/, 1]
12
+ tag = "v#{version}"
13
+ gem_file = "pkg/cumo-#{version}.gem"
14
+ remote = args[:remote] || "origin"
15
+ raise "There are files that need to be committed first." unless system("git", "diff", "--quiet") && system("git", "diff-index", "--quiet", "--cached", "HEAD")
16
+
17
+ tagged = git.call("rev-parse", "-q", "--verify", "#{tag}^{commit}")
18
+ raise "#{tag} is on #{tagged[0, 8]}, not on HEAD." unless tagged.empty? || tagged == git.call("rev-parse", "HEAD")
19
+
20
+ sh "git", "tag", "-m", "Version #{version}", tag if tagged.empty?
21
+ begin
22
+ mkdir_p "pkg"
23
+ sh "gem", "build", "cumo.gemspec", "--output", gem_file
24
+ revision = Gem::Package.new(gem_file).spec.metadata["cumo_revision"]
25
+ raise "#{gem_file} records #{revision.inspect} as cumo_revision, not #{tag.inspect}." unless revision == tag
26
+ rescue StandardError
27
+ sh "git", "tag", "-d", tag if tagged.empty?
28
+ raise
29
+ end
30
+ sh "git", "push", remote, "HEAD"
31
+ sh "git", "push", remote, "refs/tags/#{tag}"
32
+ sh "gem", "push", gem_file unless ENV["gem_push"] == "no"
33
+ end
5
34
 
6
35
  Rake::TestTask.new(:test) do |t|
7
36
  t.libs << "test"
@@ -10,7 +39,9 @@ Rake::TestTask.new(:test) do |t|
10
39
  end
11
40
 
12
41
  require "rake/extensiontask"
13
- Rake::ExtensionTask.new("cumo")
42
+ ext = Rake::ExtensionTask.new("cumo")
43
+ # The Makefile lists an object for each kernel part, so a new part needs extconf.rb to run again.
44
+ file "#{ext.tmp_dir}/#{ext.platform}/#{ext.name}/#{RUBY_VERSION}/Makefile" => "ext/cumo/narray/gen/kernel_parts.rb"
14
45
 
15
46
  desc 'Build the C++ memory pool test harness without running it (running it needs a GPU)'
16
47
  task :build_ctest do
@@ -0,0 +1,26 @@
1
+ #include <cub/util_device.cuh>
2
+ #include "cumo/template_kernel.h"
3
+
4
+ namespace {
5
+
6
+ __global__ void cumo_cub_ptx_version_probe() {}
7
+
8
+ } // namespace
9
+
10
+ // CUB learns each device's PTX version once, from the attributes of an empty
11
+ // kernel. Each translation unit has its own empty kernel, but the link keeps
12
+ // one copy of the code that asks. The query loads the module that copy came
13
+ // from, which can be a large one the caller never runs. This module has one
14
+ // kernel built with the same flags, so it gives the same version at little cost.
15
+ extern "C" void
16
+ cumo_cuda_cub_prime_ptx_version(void)
17
+ {
18
+ cub::GetPerDeviceAttributeCache<cub::PtxVersionCacheTag>()(
19
+ [](int& ptx_version) {
20
+ cudaFuncAttributes attrs = {};
21
+ cudaError_t status = cudaFuncGetAttributes(&attrs, (const void*)cumo_cub_ptx_version_probe);
22
+ ptx_version = attrs.ptxVersion * 10;
23
+ return status;
24
+ },
25
+ cub::CurrentDevice());
26
+ }
data/ext/cumo/extconf.rb CHANGED
@@ -55,7 +55,6 @@ narray/ndloop
55
55
  narray/ndloop_kernel
56
56
  narray/data
57
57
  narray/data_kernel
58
- narray/sort_kernel
59
58
  narray/math
60
59
  narray/SFMT
61
60
  narray/struct
@@ -70,6 +69,7 @@ cuda/cudnn
70
69
  cuda/cudnn_impl
71
70
  cuda/cusolver
72
71
  cuda/cufft
72
+ cuda/cub
73
73
  )
74
74
  %w[bit int8 int16 int32 int64 uint8 uint16 uint32 uint64 hfloat bfloat sfloat dfloat scomplex dcomplex robject].each do |type_name|
75
75
  srcs << "narray/types/#{type_name}"
@@ -33,10 +33,11 @@ extern size_t cumo_cuda_sync_epoch;
33
33
  // and reads again once the count moves.
34
34
  extern size_t cumo_cuda_launch_epoch;
35
35
 
36
- static inline void
36
+ // Answers the count before the write.
37
+ static inline size_t
37
38
  cumo_cuda_runtime_note_device_write(void)
38
39
  {
39
- cumo_atomic_size_fetch_add(&cumo_cuda_launch_epoch, 1);
40
+ return cumo_atomic_size_fetch_add(&cumo_cuda_launch_epoch, 1);
40
41
  }
41
42
 
42
43
  // A pinned host buffer for reading device memory back: a copy into pinned
@@ -104,6 +104,13 @@ typedef struct {
104
104
  // it will read next again if device memory was written since it was staged.
105
105
  void cumo_na_ndloop_refresh_next(cumo_na_loop_t *lp, const void *next, size_t bytes);
106
106
 
107
+ // For a function that yields to Ruby and writes an array that was there
108
+ // before it, which cumo_na_ndloop_writes_existing answers: counts the element
109
+ // it just wrote, so that a loop it yields to before it ends reads it. A staged
110
+ // copy of its own has to hold the element already.
111
+ int cumo_na_ndloop_writes_existing(cumo_na_loop_t *lp);
112
+ void cumo_na_ndloop_wrote(cumo_na_loop_t *lp);
113
+
107
114
  // spec of user function
108
115
  typedef struct {
109
116
  cumo_na_iter_func_t func; // user function
@@ -1,3 +1,6 @@
1
+ #ifndef CUMO_SORT_KERNEL_H
2
+ #define CUMO_SORT_KERNEL_H
3
+
1
4
  #include "cumo/narray_kernel.h"
2
5
  #include "cumo/indexer.h"
3
6
  #include "cumo/template_kernel.h"
@@ -146,6 +149,7 @@ cudaError_t sort_pairs(const Key* kin, Key* kout, const Value* vin, Value* vout,
146
149
  int64_t total, int64_t n_rows, int64_t row_len) {
147
150
  size_t bytes = 0;
148
151
  cudaError_t st = cudaSuccess;
152
+ cumo_cuda_cub_prime_ptx_version();
149
153
  if (n_rows == 1) {
150
154
  st = cub::DeviceRadixSort::SortPairs(nullptr, bytes, kin, kout, vin, vout, total, 0, sizeof(Key) * 8, cumo_cuda_stream());
151
155
  if (st != cudaSuccess) { return st; }
@@ -168,6 +172,7 @@ template <typename Key>
168
172
  cudaError_t sort_keys(const Key* kin, Key* kout, int64_t total, int64_t n_rows, int64_t row_len) {
169
173
  size_t bytes = 0;
170
174
  cudaError_t st = cudaSuccess;
175
+ cumo_cuda_cub_prime_ptx_version();
171
176
  if (n_rows == 1) {
172
177
  st = cub::DeviceRadixSort::SortKeys(nullptr, bytes, kin, kout, total, 0, sizeof(Key) * 8, cumo_cuda_stream());
173
178
  if (st != cudaSuccess) { return st; }
@@ -329,6 +334,12 @@ __global__ void iota_kernel(I* out, int64_t n) {
329
334
  }
330
335
  }
331
336
 
337
+ // The positions go to CUB as this rather than as a bare integer, so that
338
+ // SortPairs is a function of this file alone. With a bare integer it is the
339
+ // same function for every type whose key has the same width, and the link
340
+ // keeps one, so SFloat would launch the kernels of the UInt32 module.
341
+ template <typename I> struct position { I value; };
342
+
332
343
  // perm[i] is the position the i-th smallest element sat at, and the index
333
344
  // array holds the number to answer for that position.
334
345
  template <int NDIM, typename I>
@@ -373,13 +384,13 @@ void sort_index_rows(cumo_na_iarray_t* a, cumo_na_indexer_t* indexer, cumo_na_ia
373
384
  key_t* kout = (key_t*)cumo_cuda_runtime_malloc(sizeof(key_t) * total);
374
385
  float_key_kernel<T><<<grid_dim, block_dim, 0, cumo_cuda_stream()>>>(data, kin, total);
375
386
  cumo_check_launch_holding(kin, kout, pin, pout, gathered);
376
- cumo_check_status_holding(sort_pairs(kin, kout, pin, pout, total, n_rows, row_len),
387
+ cumo_check_status_holding(sort_pairs(kin, kout, (const position<I>*)pin, (position<I>*)pout, total, n_rows, row_len),
377
388
  kin, kout, pin, pout, gathered);
378
389
  cumo_cuda_runtime_free((char*)kout);
379
390
  cumo_cuda_runtime_free((char*)kin);
380
391
  } else {
381
392
  T* kout = (T*)cumo_cuda_runtime_malloc(sizeof(T) * total);
382
- cumo_check_status_holding(sort_pairs(data, kout, pin, pout, total, n_rows, row_len),
393
+ cumo_check_status_holding(sort_pairs(data, kout, (const position<I>*)pin, (position<I>*)pout, total, n_rows, row_len),
383
394
  kout, pin, pout, gathered);
384
395
  cumo_cuda_runtime_free((char*)kout);
385
396
  }
@@ -396,7 +407,7 @@ void sort_index_rows(cumo_na_iarray_t* a, cumo_na_indexer_t* indexer, cumo_na_ia
396
407
 
397
408
  } // namespace
398
409
 
399
- // float_key is only defined for the two float types, so the integer entry
410
+ // float_key is only defined for the float types, so the integer entry
400
411
  // points must not instantiate it; the bool picks the branch at compile time.
401
412
  #define CUMO_DEF_SORT(name, type, is_float) \
402
413
  extern "C" void cumo_##name##_sort_kernel_launch( \
@@ -420,15 +431,4 @@ void sort_index_rows(cumo_na_iarray_t* a, cumo_na_indexer_t* indexer, cumo_na_ia
420
431
  } \
421
432
  }
422
433
 
423
- CUMO_DEF_SORT(int8, int8_t, false)
424
- CUMO_DEF_SORT(int16, int16_t, false)
425
- CUMO_DEF_SORT(int32, int32_t, false)
426
- CUMO_DEF_SORT(int64, int64_t, false)
427
- CUMO_DEF_SORT(uint8, u_int8_t, false)
428
- CUMO_DEF_SORT(uint16, u_int16_t, false)
429
- CUMO_DEF_SORT(uint32, u_int32_t, false)
430
- CUMO_DEF_SORT(uint64, u_int64_t, false)
431
- CUMO_DEF_SORT(hfloat, cumo_half, true)
432
- CUMO_DEF_SORT(bfloat, cumo_bfloat, true)
433
- CUMO_DEF_SORT(sfloat, float, true)
434
- CUMO_DEF_SORT(dfloat, double, true)
434
+ #endif /* ifndef CUMO_SORT_KERNEL_H */
@@ -186,6 +186,8 @@ void cumo_cuda_runtime_check_kernel_launch(void);
186
186
  // The stream every launch and copy goes to. It is 0 until Stream#with says
187
187
  // otherwise, and thread local, so one thread's choice is not another's.
188
188
  struct CUstream_st* cumo_cuda_stream(void);
189
+ // Call before any CUB or Thrust algorithm. Defined in cuda/cub.cu, which says why.
190
+ void cumo_cuda_cub_prime_ptx_version(void);
189
191
 
190
192
  // The forms to call while scratch is held, which free what they are given
191
193
  // before they raise. Pass every buffer that is still outstanding, including the
@@ -158,6 +158,9 @@ cumo_na_swap_byte(VALUE self)
158
158
  CUMO_STRIDE_LOOP|CUMO_NDF_INDEXER_LOOP|CUMO_NDF_ACCEPT_BYTESWAP,
159
159
  1, 1, ain, aout };
160
160
 
161
+ if (rb_obj_is_kind_of(self, cumo_cRObject)) {
162
+ rb_raise(rb_eTypeError, "cannot swap bytes of %s", rb_obj_classname(self));
163
+ }
161
164
  // Cumo::Bit's element is one bit, so swapping the bytes of an element
162
165
  // changes nothing, and a loop stepping by the element stride would walk
163
166
  // one byte per bit, eight times past the end of the packed buffer. Only
@@ -41,14 +41,14 @@ erb_dir.unshift("tmpl_bit") if (type_name == "bit")
41
41
  erb_dir.map! { |d| File.join(thisdir, d) }
42
42
 
43
43
  TEMPLATE_KERNEL_PARTS = {
44
- "accum" => %w[reduce reduce_nan],
45
- "accum_index" => %w[reduce reduce_nan],
46
- "accum_arg" => %w[reduce reduce_nan],
47
- "accum_binary" => %w[reduce reduce_nan reduce_from],
48
- "cum" => %w[reduce],
49
- "layer_norm" => %w[reduce],
50
- "rms_norm" => %w[reduce],
51
- "softmax" => %w[reduce],
44
+ "accum" => %w[reduce reduce_rare reduce_nan],
45
+ "accum_index" => %w[reduce reduce_rare reduce_nan],
46
+ "accum_arg" => %w[reduce reduce_rare reduce_nan],
47
+ "accum_binary" => %w[reduce reduce_rare reduce_nan reduce_from],
48
+ "cum" => %w[reduce_rare],
49
+ "layer_norm" => %w[reduce_rare],
50
+ "rms_norm" => %w[reduce_rare],
51
+ "softmax" => %w[reduce_rare],
52
52
  "bit_count" => %w[reduce],
53
53
  "bit_reduce" => %w[reduce],
54
54
  "bit_stat" => %w[reduce],
@@ -59,10 +59,14 @@ TEMPLATE_KERNEL_PARTS = {
59
59
  "poly" => %w[misc],
60
60
  "quantize_symmetric" => %w[misc],
61
61
  "unary_ret2" => %w[misc],
62
+ "sort" => %w[sort],
62
63
  }.freeze
63
64
 
64
65
  MATH_TEMPLATES = %w[unary_s binary_s ternary_s frexp].freeze
65
66
  COMMON_MATH = %w[sqrt log log2 log10 exp exp2 sin cos tan tanh erf gelu gelu_tanh silu sigmoid softplus].freeze
67
+ # mulsum is here for dot of two vectors.
68
+ COMMON_REDUCE = %w[sum mean max min mulsum].freeze
69
+ RARE_REDUCE = %w[prod ptp minmax var stddev rms abs_sum abs_max abs_min kahan_sum min_index max_index argmin argmax].freeze
66
70
 
67
71
  abort "unknown kernel part: #{$kernel_part}" unless KERNEL_PARTS.include?($kernel_part)
68
72
  undeclared_math = COMMON_MATH - File.read(File.join(thisdir, "spec.rb")).scan(/^\s*math "(\w+)"/).flatten
@@ -83,8 +87,13 @@ class ErbPP
83
87
  @children.select { |c| c.kernel_parts.include?($kernel_part) }
84
88
  end
85
89
 
86
- def kernel_part?(nan: false, from: nil)
87
- $kernel_part == (from ? "reduce_from" : nan ? "reduce_nan" : "reduce")
90
+ def kernel_part?(op = nil, nan: false, from: nil)
91
+ return $kernel_part == "reduce_from" if from
92
+ return $kernel_part == "reduce_nan" if nan
93
+ return $kernel_part == "reduce" if COMMON_REDUCE.include?(op)
94
+ return $kernel_part == "reduce_rare" if RARE_REDUCE.include?(op)
95
+
96
+ raise ArgumentError, "#{op.inspect} is in neither COMMON_REDUCE nor RARE_REDUCE"
88
97
  end
89
98
 
90
99
  def indexer_dims(narrow = false)
@@ -1,14 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- KERNEL_PARTS = %w[main reduce reduce_nan reduce_from math math_rare compare cast misc].freeze
3
+ KERNEL_PARTS = %w[main reduce reduce_nan reduce_from reduce_rare math math_rare compare cast misc sort].freeze
4
4
  MATH_TYPES = %w[hfloat bfloat sfloat dfloat scomplex dcomplex].freeze
5
+ SORT_TYPES = %w[int8 int16 int32 int64 uint8 uint16 uint32 uint64 hfloat bfloat sfloat dfloat].freeze
5
6
 
6
7
  def kernel_parts_of(type_name)
7
- return KERNEL_PARTS.first(4) if type_name == "robject"
8
+ return %w[main reduce reduce_nan reduce_from] if type_name == "robject"
8
9
 
9
10
  KERNEL_PARTS.reject do |part|
10
11
  (%w[math math_rare].include?(part) && !MATH_TYPES.include?(type_name)) ||
11
- (%w[compare misc].include?(part) && type_name == "bit")
12
+ (%w[compare misc reduce_rare].include?(part) && type_name == "bit") ||
13
+ (part == "sort" && !SORT_TYPES.include?(type_name))
12
14
  end
13
15
  end
14
16
 
@@ -100,8 +100,12 @@ end
100
100
 
101
101
  if is_object
102
102
  undef_singleton_method "from_binary"
103
+ undef_singleton_method "from_string"
104
+ undef_method "store_binary"
103
105
  undef_method "to_binary"
106
+ undef_method "to_string"
104
107
  undef_method "swap_byte"
108
+ undef_method "hton"
105
109
  undef_method "to_network"
106
110
  undef_method "to_vacs"
107
111
  undef_method "to_host"
@@ -119,7 +119,7 @@ extern "C" {
119
119
  #endif
120
120
  #endif
121
121
 
122
- <% if kernel_part? %>
122
+ <% if kernel_part?("argmin") %>
123
123
  void cumo_<%=type_name%>_argmin_int<%=i%>_kernel_launch(cumo_na_reduction_arg_t* arg)
124
124
  {
125
125
  cumo_reduce_arg_split<dtype, idx_t, cumo_<%=type_name%>_argmin_int<%=i%>_impl>(*arg, cumo_<%=type_name%>_argmin_int<%=i%>_impl{});
@@ -133,7 +133,7 @@ void cumo_<%=type_name%>_argmin_nan_int<%=i%>_kernel_launch(cumo_na_reduction_ar
133
133
  }
134
134
  <% end %>
135
135
 
136
- <% if kernel_part? %>
136
+ <% if kernel_part?("argmax") %>
137
137
  void cumo_<%=type_name%>_argmax_int<%=i%>_kernel_launch(cumo_na_reduction_arg_t* arg)
138
138
  {
139
139
  cumo_reduce_arg_split<dtype, idx_t, cumo_<%=type_name%>_argmax_int<%=i%>_impl>(*arg, cumo_<%=type_name%>_argmax_int<%=i%>_impl{});
@@ -63,7 +63,7 @@ extern "C" {
63
63
  //<% (is_float ? ["","_nan"] : [""]).each do |nan| %>
64
64
  //<% ([nil] + from_types).each do |from| %>
65
65
  //<% sfx = from ? "_from_#{from[:id]}" : "" %>
66
- //<% if kernel_part?(nan: nan == "_nan", from: from) %>
66
+ //<% if kernel_part?(name, nan: nan == "_nan", from: from) %>
67
67
  void <%="cumo_#{type_name}_#{name}#{nan}#{sfx}_kernel_launch"%>(cumo_na_reduction_arg_t* arg, cumo_na_iarray_t* in2)
68
68
  {
69
69
  cumo_reduce_zip_split<dtype, <%= from ? from[:ctype] : "dtype" %>, dtype, <%="cumo_#{type_name}_#{name}#{nan}#{sfx}_impl"%>>(*arg, *in2, <%="cumo_#{type_name}_#{name}#{nan}#{sfx}_impl"%>{});
@@ -119,7 +119,7 @@ extern "C" {
119
119
  #endif
120
120
  #endif
121
121
 
122
- <% if kernel_part? %>
122
+ <% if kernel_part?("min_index") %>
123
123
  void cumo_<%=type_name%>_min_index_int<%=i%>_kernel_launch(cumo_na_reduction_arg_t* arg)
124
124
  {
125
125
  cumo_reduce_split<dtype, idx_t, cumo_<%=type_name%>_min_index_int<%=i%>_impl>(*arg, cumo_<%=type_name%>_min_index_int<%=i%>_impl{});
@@ -133,7 +133,7 @@ void cumo_<%=type_name%>_min_index_nan_int<%=i%>_kernel_launch(cumo_na_reduction
133
133
  }
134
134
  <% end %>
135
135
 
136
- <% if kernel_part? %>
136
+ <% if kernel_part?("max_index") %>
137
137
  void cumo_<%=type_name%>_max_index_int<%=i%>_kernel_launch(cumo_na_reduction_arg_t* arg)
138
138
  {
139
139
  cumo_reduce_split<dtype, idx_t, cumo_<%=type_name%>_max_index_int<%=i%>_impl>(*arg, cumo_<%=type_name%>_max_index_int<%=i%>_impl{});
@@ -10,6 +10,13 @@
10
10
 
11
11
  <% unless type_name == 'robject' %>
12
12
  void <%="cumo_#{c_iter}_kernel_launch"%>(cumo_na_iarray_t* a1, cumo_na_iarray_t* a2, cumo_na_iarray_t* a3, cumo_na_indexer_t* indexer, int* divzero);
13
+ //<% if is_complex && %w[add sub mul div].include?(name) %>
14
+ void <%="cumo_#{c_iter}_r_kernel_launch"%>(cumo_na_iarray_t* a1, cumo_na_iarray_t* a2, cumo_na_iarray_t* a3, cumo_na_indexer_t* indexer, int real_is_left);
15
+ //<% end %>
16
+ //<% complex_of = {'sfloat' => ['scomplex', 'cumo_cSComplex'], 'dfloat' => ['dcomplex', 'cumo_cDComplex']}[type_name] if %w[add sub mul div].include?(name) %>
17
+ //<% if complex_of %>
18
+ VALUE <%="cumo_#{complex_of[0]}_#{name}_real_left"%>(VALUE real, VALUE complex);
19
+ //<% end %>
13
20
  //<% if %w[add sub mul div mod bit_and bit_or bit_xor left_shift right_shift copysign].include?(name) %>
14
21
  void <%="cumo_#{c_iter}_s_kernel_launch"%>(cumo_na_iarray_t* a1, dtype sv, cumo_na_iarray_t* a3, cumo_na_indexer_t* indexer, int* divzero, int scalar_is_left);
15
22
  //<% end %>
@@ -171,6 +178,40 @@ static void
171
178
  //<% end %>
172
179
  #undef check_intdivzero
173
180
 
181
+ //<% if is_complex && %w[add sub mul div].include?(name) %>
182
+ // The operands stay in the order they were written in, so that a shape
183
+ // mismatch names them as it always did.
184
+ static void
185
+ <%=c_iter%>_r(cumo_na_loop_t *const lp)
186
+ {
187
+ int real_is_left = *(int*)(lp->opt_ptr);
188
+ cumo_na_iarray_t a1 = cumo_na_make_iarray(&lp->args[real_is_left ? 1 : 0]);
189
+ cumo_na_iarray_t a2 = cumo_na_make_iarray(&lp->args[real_is_left ? 0 : 1]);
190
+ cumo_na_iarray_t a3 = cumo_na_make_iarray(&lp->args[2]);
191
+ cumo_na_indexer_t indexer = cumo_na_make_indexer(&lp->args[0]);
192
+
193
+ <%="cumo_#{c_iter}_r_kernel_launch"%>(&a1,&a2,&a3,&indexer,real_is_left);
194
+ }
195
+
196
+ // A real operand of this precision is read as it is rather than cast to a
197
+ // complex array first.
198
+ static VALUE
199
+ <%=c_func%>_real(VALUE lhs, VALUE rhs, int real_is_left)
200
+ {
201
+ cumo_ndfunc_arg_in_t ain[2] = {{real_is_left ? cRT : cT,0},{real_is_left ? cT : cRT,0}};
202
+ cumo_ndfunc_arg_out_t aout[1] = {{cT,0}};
203
+ cumo_ndfunc_t ndf = { <%=c_iter%>_r, CUMO_STRIDE_LOOP|CUMO_NDF_INDEXER_LOOP|CUMO_NDF_ANY_ORDER, 2, 1, ain, aout };
204
+
205
+ return cumo_na_ndloop3(&ndf, &real_is_left, 2, lhs, rhs);
206
+ }
207
+
208
+ VALUE
209
+ <%="cumo_#{type_name}_#{name}_real_left"%>(VALUE real, VALUE self)
210
+ {
211
+ return <%=c_func%>_real(real, self, 1);
212
+ }
213
+ //<% end %>
214
+
174
215
  static VALUE
175
216
  <%=c_func%>_self(VALUE self, VALUE other)
176
217
  {
@@ -202,6 +243,11 @@ static VALUE
202
243
  return cumo_na_ndloop3(&ndf_s, &y, 1, self);
203
244
  }
204
245
  //<% end %>
246
+ //<% if is_complex && %w[add sub mul div].include?(name) %>
247
+ if (rb_obj_class(other) == cRT) {
248
+ return <%=c_func%>_real(self, other, 0);
249
+ }
250
+ //<% end %>
205
251
  cumo_ndfunc_arg_in_t ain[2] = {{cT,0},{cT,0}};
206
252
  cumo_ndfunc_arg_out_t aout[1] = {{cT,0}};
207
253
  <% if type_name == 'robject' %>
@@ -230,6 +276,10 @@ static VALUE
230
276
  klass = cumo_na_upcast(rb_obj_class(self),rb_obj_class(other));
231
277
  if (klass==cT) {
232
278
  return <%=c_func%>_self(self, other);
279
+ //<% if complex_of %>
280
+ } else if (rb_obj_class(other) == <%=complex_of[1]%> && rb_obj_class(self) == cT) {
281
+ return <%="cumo_#{complex_of[0]}_#{name}_real_left"%>(self, other);
282
+ //<% end %>
233
283
  } else {
234
284
  v = rb_funcall(klass, cumo_id_cast, 1, self);
235
285
  return rb_funcall(v, <%=cumo_id_op%>, 1, other);
@@ -214,5 +214,29 @@ void <%="cumo_#{c_iter}_kernel_launch"%>(cumo_na_iarray_t* a1, cumo_na_iarray_t*
214
214
  cumo_cuda_runtime_check_kernel_launch();
215
215
  }
216
216
  //<% end %>
217
+
218
+ //<% if is_complex && %w[add sub mul div].include?(name) %>
219
+ // A real operand of the same precision is made complex as it is read, which is
220
+ // what casting it first did, without the array the cast wrote.
221
+ <% indexer_dims(true).each do |idim| %>
222
+ __global__ void <%="cumo_#{c_iter}_r_kernel_dim#{idim}"%>(cumo_na_iarray_t a1, cumo_na_iarray_t a2, cumo_na_iarray_t a3, cumo_na_indexer_t indexer, int real_is_left)
223
+ {
224
+ for (uint64_t i = blockIdx.x * blockDim.x + threadIdx.x; i < indexer.total_size; i += blockDim.x * gridDim.x) {
225
+ cumo_na_indexer_set_dim<%=idim%>(&indexer, i);
226
+ dtype x = *(dtype*)(cumo_na_iarray_at_dim<%=idim%>(&a1, &indexer));
227
+ dtype y = m_from_real(*(rtype*)(cumo_na_iarray_at_dim<%=idim%>(&a2, &indexer)));
228
+ *(dtype*)(cumo_na_iarray_at_dim<%=idim%>(&a3, &indexer)) = real_is_left ? m_<%=name%>(y, x) : m_<%=name%>(x, y);
229
+ }
230
+ }
231
+ <% end %>
232
+
233
+ void <%="cumo_#{c_iter}_r_kernel_launch"%>(cumo_na_iarray_t* a1, cumo_na_iarray_t* a2, cumo_na_iarray_t* a3, cumo_na_indexer_t* indexer, int real_is_left)
234
+ {
235
+ size_t grid_dim = cumo_get_grid_dim(indexer->total_size);
236
+ size_t block_dim = cumo_get_block_dim(indexer->total_size);
237
+ <%= indexer_switch("cumo_#{c_iter}_r_kernel", "*a1,*a2,*a3,*indexer,real_is_left", narrow: %w[a1 a2 a3]) %>
238
+ cumo_cuda_runtime_check_kernel_launch();
239
+ }
240
+ //<% end %>
217
241
  #undef cumo_check_intdivzero
218
242
  <% end %>
@@ -215,33 +215,43 @@ extern "C" {
215
215
  #endif
216
216
  #endif
217
217
 
218
- <% if kernel_part? %>
218
+ <% if kernel_part?("sum") %>
219
219
  void cumo_<%=type_name%>_sum_kernel_launch(cumo_na_reduction_arg_t* arg)
220
220
  {
221
221
  cumo_reduce_split<dtype, <%=dtype%>, cumo_<%=type_name%>_sum_impl>(*arg, cumo_<%=type_name%>_sum_impl{});
222
222
  }
223
+ <% end %>
223
224
 
225
+ <% if kernel_part?("prod") %>
224
226
  void cumo_<%=type_name%>_prod_kernel_launch(cumo_na_reduction_arg_t* arg)
225
227
  {
226
228
  cumo_reduce_split<dtype, <%=dtype%>, cumo_<%=type_name%>_prod_impl>(*arg, cumo_<%=type_name%>_prod_impl{});
227
229
  }
230
+ <% end %>
228
231
 
232
+ <% if kernel_part?("mean") %>
229
233
  void cumo_<%=type_name%>_mean_kernel_launch(cumo_na_reduction_arg_t* arg)
230
234
  {
231
235
  rtype n = (rtype)(arg->in_indexer.total_size / arg->out_indexer.total_size);
232
236
  cumo_reduce_split<dtype, dtype, cumo_<%=type_name%>_mean_impl>(*arg, cumo_<%=type_name%>_mean_impl{n});
233
237
  }
238
+ <% end %>
234
239
 
240
+ <% if kernel_part?("var") %>
235
241
  void cumo_<%=type_name%>_var_kernel_launch(cumo_na_reduction_arg_t* arg)
236
242
  {
237
243
  cumo_reduce_split<dtype, rtype, cumo_<%=type_name%>_var_impl>(*arg, cumo_<%=type_name%>_var_impl{});
238
244
  }
245
+ <% end %>
239
246
 
247
+ <% if kernel_part?("stddev") %>
240
248
  void cumo_<%=type_name%>_stddev_kernel_launch(cumo_na_reduction_arg_t* arg)
241
249
  {
242
250
  cumo_reduce_split<dtype, rtype, cumo_<%=type_name%>_stddev_impl>(*arg, cumo_<%=type_name%>_stddev_impl{});
243
251
  }
252
+ <% end %>
244
253
 
254
+ <% if kernel_part?("rms") %>
245
255
  void cumo_<%=type_name%>_rms_kernel_launch(cumo_na_reduction_arg_t* arg)
246
256
  {
247
257
  rtype n = (rtype)(arg->in_indexer.total_size / arg->out_indexer.total_size);
@@ -280,25 +290,29 @@ void cumo_<%=type_name%>_rms_nan_kernel_launch(cumo_na_reduction_arg_t* arg)
280
290
  cumo_reduce_split<dtype, rtype, cumo_<%=type_name%>_rms_nan_impl>(*arg, cumo_<%=type_name%>_rms_nan_impl{});
281
291
  }
282
292
  <% end %>
283
- <% if kernel_part? %>
293
+ <% if kernel_part?("abs_sum") %>
284
294
  void cumo_<%=type_name%>_abs_sum_kernel_launch(cumo_na_reduction_arg_t* arg)
285
295
  {
286
296
  cumo_reduce_split<dtype, rtype, cumo_<%=type_name%>_abs_sum_impl>(*arg, cumo_<%=type_name%>_abs_sum_impl{});
287
297
  }
298
+ <% end %>
288
299
 
300
+ <% if kernel_part?("abs_max") %>
289
301
  void cumo_<%=type_name%>_abs_max_kernel_launch(cumo_na_reduction_arg_t* arg)
290
302
  {
291
303
  using impl = cumo_<%=type_name%>_abs_extremum_of<cumo_<%=type_name%>_abs_extremum_rules::Max>;
292
304
  cumo_reduce_split<dtype, rtype, impl>(*arg, impl{});
293
305
  }
306
+ <% end %>
294
307
 
308
+ <% if kernel_part?("abs_min") %>
295
309
  void cumo_<%=type_name%>_abs_min_kernel_launch(cumo_na_reduction_arg_t* arg)
296
310
  {
297
311
  using impl = cumo_<%=type_name%>_abs_extremum_of<cumo_<%=type_name%>_abs_extremum_rules::Min>;
298
312
  cumo_reduce_split<dtype, rtype, impl>(*arg, impl{});
299
313
  }
300
314
  <% end %>
301
- <% if is_double_precision && kernel_part? %>
315
+ <% if is_double_precision && kernel_part?("kahan_sum") %>
302
316
 
303
317
  void cumo_<%=type_name%>_kahan_sum_kernel_launch(cumo_na_reduction_arg_t* arg)
304
318
  {