cumo 0.7.0 → 0.9.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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +590 -152
  3. data/CHANGELOG.md +63 -0
  4. data/README.md +245 -0
  5. data/bench/cumo_probe.rb +1 -1
  6. data/cumo.gemspec +7 -1
  7. data/ext/cumo/cuda/cudnn.c +18 -0
  8. data/ext/cumo/cuda/cudnn_impl.cpp +60 -14
  9. data/ext/cumo/cuda/driver.c +11 -9
  10. data/ext/cumo/cuda/memory_pool_impl.cpp +32 -22
  11. data/ext/cumo/cuda/memory_pool_impl.hpp +3 -1
  12. data/ext/cumo/cuda/memory_pool_impl_test.cpp +19 -25
  13. data/ext/cumo/cuda/runtime.c +56 -0
  14. data/ext/cumo/cumo.c +4 -0
  15. data/ext/cumo/extconf.rb +2 -0
  16. data/ext/cumo/include/cumo/bit_reduce_kernel.h +6 -6
  17. data/ext/cumo/include/cumo/check.h +11 -3
  18. data/ext/cumo/include/cumo/cuda/cudnn.h +43 -6
  19. data/ext/cumo/include/cumo/cuda/runtime.h +2 -0
  20. data/ext/cumo/include/cumo/indexer.h +16 -0
  21. data/ext/cumo/include/cumo/intern.h +11 -0
  22. data/ext/cumo/include/cumo/narray.h +1 -0
  23. data/ext/cumo/include/cumo/reduce_kernel.h +283 -54
  24. data/ext/cumo/include/cumo/row_kernel.h +202 -0
  25. data/ext/cumo/include/cumo/row_method.h +149 -0
  26. data/ext/cumo/include/cumo/template_kernel.h +26 -0
  27. data/ext/cumo/include/cumo/types/bf16_def.h +82 -0
  28. data/ext/cumo/include/cumo/types/bf16_def_kernel.h +28 -0
  29. data/ext/cumo/include/cumo/types/bf16_macro.h +14 -0
  30. data/ext/cumo/include/cumo/types/bf16_macro_kernel.h +19 -0
  31. data/ext/cumo/include/cumo/types/bfloat.h +22 -0
  32. data/ext/cumo/include/cumo/types/bfloat_kernel.h +16 -0
  33. data/ext/cumo/include/cumo/types/bit.h +2 -0
  34. data/ext/cumo/include/cumo/types/bit_kernel.h +2 -0
  35. data/ext/cumo/include/cumo/types/complex_macro.h +2 -0
  36. data/ext/cumo/include/cumo/types/complex_macro_kernel.h +2 -0
  37. data/ext/cumo/include/cumo/types/f16_macro.h +191 -0
  38. data/ext/cumo/include/cumo/types/f16_macro_kernel.h +214 -0
  39. data/ext/cumo/include/cumo/types/float_def.h +8 -0
  40. data/ext/cumo/include/cumo/types/float_def_kernel.h +8 -0
  41. data/ext/cumo/include/cumo/types/float_macro.h +26 -17
  42. data/ext/cumo/include/cumo/types/float_macro_kernel.h +26 -19
  43. data/ext/cumo/include/cumo/types/half_macro.h +6 -180
  44. data/ext/cumo/include/cumo/types/half_macro_kernel.h +6 -185
  45. data/ext/cumo/include/cumo/types/robj_macro.h +2 -0
  46. data/ext/cumo/include/cumo/types/xint_macro.h +2 -6
  47. data/ext/cumo/include/cumo/types/xint_macro_kernel.h +2 -0
  48. data/ext/cumo/include/cumo.h +2 -2
  49. data/ext/cumo/narray/data.c +15 -6
  50. data/ext/cumo/narray/data_kernel.cu +110 -0
  51. data/ext/cumo/narray/gen/def/bfloat.rb +57 -0
  52. data/ext/cumo/narray/gen/def/bit.rb +12 -1
  53. data/ext/cumo/narray/gen/def/dcomplex.rb +12 -1
  54. data/ext/cumo/narray/gen/def/dfloat.rb +11 -1
  55. data/ext/cumo/narray/gen/def/hfloat.rb +11 -1
  56. data/ext/cumo/narray/gen/def/int16.rb +12 -1
  57. data/ext/cumo/narray/gen/def/int32.rb +12 -1
  58. data/ext/cumo/narray/gen/def/int64.rb +12 -1
  59. data/ext/cumo/narray/gen/def/int8.rb +12 -1
  60. data/ext/cumo/narray/gen/def/robject.rb +12 -1
  61. data/ext/cumo/narray/gen/def/scomplex.rb +12 -1
  62. data/ext/cumo/narray/gen/def/sfloat.rb +11 -1
  63. data/ext/cumo/narray/gen/def/uint16.rb +12 -1
  64. data/ext/cumo/narray/gen/def/uint32.rb +12 -1
  65. data/ext/cumo/narray/gen/def/uint64.rb +12 -1
  66. data/ext/cumo/narray/gen/def/uint8.rb +12 -1
  67. data/ext/cumo/narray/gen/narray_def.rb +35 -6
  68. data/ext/cumo/narray/gen/spec.rb +14 -14
  69. data/ext/cumo/narray/gen/tmpl/accum_arg.c +1 -1
  70. data/ext/cumo/narray/gen/tmpl/accum_binary.c +63 -5
  71. data/ext/cumo/narray/gen/tmpl/accum_binary_kernel.cu +29 -13
  72. data/ext/cumo/narray/gen/tmpl/accum_index.c +1 -1
  73. data/ext/cumo/narray/gen/tmpl/batch_norm.c +4 -4
  74. data/ext/cumo/narray/gen/tmpl/batch_norm_backward.c +5 -5
  75. data/ext/cumo/narray/gen/tmpl/binary.c +3 -9
  76. data/ext/cumo/narray/gen/tmpl/complex_accum_kernel.cu +15 -5
  77. data/ext/cumo/narray/gen/tmpl/conv.c +90 -123
  78. data/ext/cumo/narray/gen/tmpl/conv_grad_w.c +91 -83
  79. data/ext/cumo/narray/gen/tmpl/conv_transpose.c +90 -123
  80. data/ext/cumo/narray/gen/tmpl/cum.c +8 -7
  81. data/ext/cumo/narray/gen/tmpl/cum_kernel.cu +7 -6
  82. data/ext/cumo/narray/gen/tmpl/fixed_batch_norm.c +4 -4
  83. data/ext/cumo/narray/gen/tmpl/float_accum_kernel.cu +22 -10
  84. data/ext/cumo/narray/gen/tmpl/gemm.c +11 -41
  85. data/ext/cumo/narray/gen/tmpl/layer_norm.c +38 -0
  86. data/ext/cumo/narray/gen/tmpl/layer_norm_kernel.cu +54 -0
  87. data/ext/cumo/narray/gen/tmpl/lib.c +1 -0
  88. data/ext/cumo/narray/gen/tmpl/lib_kernel.cu +1 -0
  89. data/ext/cumo/narray/gen/tmpl/median.c +1 -1
  90. data/ext/cumo/narray/gen/tmpl/poly.c +0 -3
  91. data/ext/cumo/narray/gen/tmpl/poly_kernel.cu +1 -1
  92. data/ext/cumo/narray/gen/tmpl/pooling_backward.c +3 -2
  93. data/ext/cumo/narray/gen/tmpl/pooling_forward.c +3 -2
  94. data/ext/cumo/narray/gen/tmpl/quantize_symmetric.c +74 -0
  95. data/ext/cumo/narray/gen/tmpl/quantize_symmetric_kernel.cu +67 -0
  96. data/ext/cumo/narray/gen/tmpl/rand.c +9 -9
  97. data/ext/cumo/narray/gen/tmpl/rand_kernel.cu +9 -9
  98. data/ext/cumo/narray/gen/tmpl/rand_norm.c +4 -4
  99. data/ext/cumo/narray/gen/tmpl/rand_norm_kernel.cu +7 -7
  100. data/ext/cumo/narray/gen/tmpl/real_accum_kernel.cu +6 -6
  101. data/ext/cumo/narray/gen/tmpl/rms_norm.c +38 -0
  102. data/ext/cumo/narray/gen/tmpl/rms_norm_kernel.cu +57 -0
  103. data/ext/cumo/narray/gen/tmpl/softmax.c +21 -0
  104. data/ext/cumo/narray/gen/tmpl/softmax_kernel.cu +157 -0
  105. data/ext/cumo/narray/gen/tmpl/sort.c +9 -32
  106. data/ext/cumo/narray/gen/tmpl/sort_index.c +16 -95
  107. data/ext/cumo/narray/gen/tmpl/store_array.c +12 -9
  108. data/ext/cumo/narray/gen/tmpl/store_from.c +1 -16
  109. data/ext/cumo/narray/gen/tmpl_bit/mask.c +11 -11
  110. data/ext/cumo/narray/gen/tmpl_bit/store_array.c +5 -9
  111. data/ext/cumo/narray/gen/tmpl_bit/where.c +50 -11
  112. data/ext/cumo/narray/gen/tmpl_bit/where2.c +11 -12
  113. data/ext/cumo/narray/gen/tmpl_bit/where_kernel.cu +1 -2
  114. data/ext/cumo/narray/index.c +35 -25
  115. data/ext/cumo/narray/index_kernel.cu +27 -0
  116. data/ext/cumo/narray/math.c +40 -8
  117. data/ext/cumo/narray/narray.c +85 -15
  118. data/ext/cumo/narray/ndloop.c +210 -5
  119. data/ext/cumo/narray/sort_kernel.cu +61 -41
  120. data/ext/cumo/narray/struct.c +3 -0
  121. data/lib/cumo/cuda/cudnn.rb +1 -1
  122. data/lib/cumo/narray/extra.rb +2 -2
  123. data/test/bfloat_test.rb +238 -0
  124. data/test/bit_test.rb +52 -14
  125. data/test/cuda/memory_pool_test.rb +8 -7
  126. data/test/cuda/runtime_test.rb +25 -0
  127. data/test/cudnn_test.rb +108 -24
  128. data/test/extra_test.rb +1 -1
  129. data/test/fused_test.rb +587 -0
  130. data/test/math_test.rb +182 -2
  131. data/test/narray_alt_coverage_test.rb +1 -0
  132. data/test/narray_test.rb +344 -22
  133. data/test/test_helper.rb +1 -0
  134. metadata +32 -5
  135. data/ext/cumo/narray/gen/tmpl/qsort.c +0 -205
@@ -69,6 +69,11 @@ typedef struct {
69
69
  bool out_flat;
70
70
  bool out2_flat;
71
71
  bool out_inner; // the out axis, not the reduce axis, runs along memory
72
+ // How many consecutive indices share one address, for a range that is flat
73
+ // once a trailing run of broadcast axes is taken off it. Zero when the
74
+ // range is not of that shape, and never 1: a divisor of 1 is in_out_flat.
75
+ int64_t in_out_div;
76
+ int64_t in_reduce_div;
72
77
  ssize_t in_out_step; // bytes, or bits for a Bit input
73
78
  ssize_t in_reduce_step; // bytes, or bits for a Bit input
74
79
  ssize_t out_step; // bytes
@@ -93,8 +98,58 @@ static inline bool axes_are_flat(const TIarray& iarray, const cumo_na_indexer_t&
93
98
  return true;
94
99
  }
95
100
 
101
+ // axes_are_flat for a range that ends in broadcast axes. A step of 0 does not
102
+ // move the address, so the offset of the i-th element is (i / div) * step,
103
+ // where div is how many indices share one address. Answers a div of 1 where
104
+ // there is no such axis, which is what axes_are_flat already describes.
105
+ template <typename TIarray>
106
+ static inline bool axes_are_flat_bcast(const TIarray& iarray, const cumo_na_indexer_t& indexer, int begin, int end, ssize_t* step, int64_t* div) {
107
+ int64_t d = 1;
108
+ while (end > begin && iarray.step[end - 1] == 0) {
109
+ d *= static_cast<int64_t>(indexer.shape[end - 1]);
110
+ --end;
111
+ }
112
+ if (!axes_are_flat(iarray, indexer, begin, end, step)) {
113
+ return false;
114
+ }
115
+ *div = d;
116
+ return true;
117
+ }
118
+
119
+ // Fills in the divisor form for a range axes_are_flat turned down, so that the
120
+ // kernel spends one division on it rather than walking every dimension.
121
+ template <typename TIarray>
122
+ static inline int64_t reduce_addr_div(bool flat, const TIarray& iarray, const cumo_na_indexer_t& indexer, int begin, int end, ssize_t* step) {
123
+ ssize_t bcast_step;
124
+ int64_t div;
125
+
126
+ // *step stays as the caller left it unless there is a divisor to go with
127
+ // it, so that the two are only ever read together.
128
+ if (flat || !axes_are_flat_bcast(iarray, indexer, begin, end, &bcast_step, &div) || div <= 1) {
129
+ return 0;
130
+ }
131
+ *step = bcast_step;
132
+ return div;
133
+ }
134
+
135
+ // The innermost step of each of the two axis groups. out_inner is decided from
136
+ // these, and a zip decides again from both operands', so they are handed back
137
+ // rather than kept in cumo_reduce_addr_t: that one is a kernel parameter, and
138
+ // the note above says what a wider one costs.
139
+ typedef struct {
140
+ ssize_t out;
141
+ ssize_t reduce;
142
+ } cumo_inner_steps_t;
143
+
144
+ // The out axis runs along memory when its step is real and shorter than the
145
+ // reduce axis's, which is what decides how a block shares its threads.
146
+ static inline bool inner_steps_say_out(const cumo_inner_steps_t& inner) {
147
+ return inner.out != 0 &&
148
+ (inner.reduce == 0 || step_magnitude(inner.out) < step_magnitude(inner.reduce));
149
+ }
150
+
96
151
  template <typename TArg>
97
- static inline cumo_reduce_addr_t make_reduce_addr(const TArg& arg, int64_t reduce_total_size) {
152
+ static inline cumo_reduce_addr_t make_reduce_addr(const TArg& arg, int64_t reduce_total_size, cumo_inner_steps_t* inner = 0) {
98
153
  cumo_reduce_addr_t ad;
99
154
  int in_ndim = arg.in_indexer.ndim;
100
155
  ssize_t whole_step;
@@ -108,6 +163,8 @@ static inline cumo_reduce_addr_t make_reduce_addr(const TArg& arg, int64_t reduc
108
163
  ad.in_reduce_flat = true;
109
164
  ad.in_reduce_step = whole_step;
110
165
  ad.in_out_step = whole_step * reduce_total_size;
166
+ ad.in_out_div = 0;
167
+ ad.in_reduce_div = 0;
111
168
  } else {
112
169
  int split = in_ndim;
113
170
  int64_t acc = 1;
@@ -119,33 +176,74 @@ static inline cumo_reduce_addr_t make_reduce_addr(const TArg& arg, int64_t reduc
119
176
  ad.split = split;
120
177
  ad.in_reduce_flat = axes_are_flat(arg.in, arg.in_indexer, split, in_ndim, &ad.in_reduce_step);
121
178
  ad.in_out_flat = axes_are_flat(arg.in, arg.in_indexer, 0, split, &ad.in_out_step);
179
+ ad.in_reduce_div = reduce_addr_div(ad.in_reduce_flat, arg.in, arg.in_indexer, split, in_ndim, &ad.in_reduce_step);
180
+ ad.in_out_div = reduce_addr_div(ad.in_out_flat, arg.in, arg.in_indexer, 0, split, &ad.in_out_step);
122
181
  } else {
123
182
  ad.split = -1;
124
183
  ad.in_reduce_flat = false;
125
184
  ad.in_out_flat = false;
126
185
  ad.in_reduce_step = 0;
127
186
  ad.in_out_step = 0;
187
+ ad.in_out_div = 0;
188
+ ad.in_reduce_div = 0;
189
+ }
190
+ // axes_are_flat leaves the step alone where it answers false, and the
191
+ // kernels work one out unconditionally, so give them a zero to read.
192
+ if (!ad.in_reduce_flat && ad.in_reduce_div == 0) {
193
+ ad.in_reduce_step = 0;
194
+ }
195
+ if (!ad.in_out_flat && ad.in_out_div == 0) {
196
+ ad.in_out_step = 0;
128
197
  }
129
198
  }
130
199
 
200
+ ad.out_step = 0;
131
201
  ad.out_flat = axes_are_flat(arg.out, arg.out_indexer, 0, arg.out_indexer.ndim, &ad.out_step);
132
202
  ad.out2_flat = true;
133
203
  ad.out2_step = 0;
134
204
 
135
- ssize_t out_inner_step, reduce_inner_step;
205
+ cumo_inner_steps_t steps;
136
206
  if (ad.in_out_flat && ad.in_reduce_flat) {
137
- out_inner_step = ad.in_out_step;
138
- reduce_inner_step = ad.in_reduce_step;
207
+ steps.out = ad.in_out_step;
208
+ steps.reduce = ad.in_reduce_step;
139
209
  } else {
140
- out_inner_step = ad.split > 0 ? arg.in.step[ad.split - 1] : 0;
141
- reduce_inner_step = (ad.split >= 0 && ad.split < in_ndim) ? arg.in.step[in_ndim - 1] : 0;
210
+ steps.out = ad.split > 0 ? arg.in.step[ad.split - 1] : 0;
211
+ steps.reduce = (ad.split >= 0 && ad.split < in_ndim) ? arg.in.step[in_ndim - 1] : 0;
212
+ }
213
+ ad.out_inner = inner_steps_say_out(steps);
214
+ if (inner != 0) {
215
+ *inner = steps;
142
216
  }
143
- ad.out_inner = out_inner_step != 0 &&
144
- (reduce_inner_step == 0 || step_magnitude(out_inner_step) < step_magnitude(reduce_inner_step));
145
217
 
146
218
  return ad;
147
219
  }
148
220
 
221
+ // The shorter of two steps, counting a zero as no step at all: an operand that
222
+ // does not move along an axis reads one address for the whole of it, so it has
223
+ // no say in which axis runs along memory.
224
+ static inline ssize_t shorter_step(ssize_t a, ssize_t b) {
225
+ if (a == 0) return b;
226
+ if (b == 0) return a;
227
+ return step_magnitude(a) < step_magnitude(b) ? a : b;
228
+ }
229
+
230
+ // A zip reduction reads both operands through one thread layout, so the layout
231
+ // has to answer for both. Taking the shorter step of the two along each axis
232
+ // gives the same answer whichever operand the caller wrote first, which a
233
+ // decision read off one of them does not.
234
+ template <typename TArg>
235
+ static inline void make_zip_reduce_addrs(const TArg& arg, const TArg& arg2, int64_t reduce_total_size,
236
+ cumo_reduce_addr_t* ad, cumo_reduce_addr_t* ad2) {
237
+ cumo_inner_steps_t inner, inner2, both;
238
+
239
+ *ad = make_reduce_addr(arg, reduce_total_size, &inner);
240
+ *ad2 = make_reduce_addr(arg2, reduce_total_size, &inner2);
241
+
242
+ both.out = shorter_step(inner.out, inner2.out);
243
+ both.reduce = shorter_step(inner.reduce, inner2.reduce);
244
+ ad->out_inner = ad2->out_inner = inner_steps_say_out(both);
245
+ }
246
+
149
247
  static inline void set_reduce_addr_out2(cumo_reduce_addr_t* ad, const cumo_na_reduction_arg_t& arg, const cumo_na_iarray_t& out2) {
150
248
  ad->out2_flat = axes_are_flat(out2, arg.out_indexer, 0, arg.out_indexer.ndim, &ad->out2_step);
151
249
  }
@@ -219,6 +317,7 @@ __device__ static __forceinline__ void axes_offset_pair(const TIarray& a, const
219
317
  template <bool FLAT>
220
318
  __device__ static __forceinline__ ssize_t reduce_in_out_offset(const cumo_na_iarray_t& in, const cumo_na_indexer_t& in_indexer, const cumo_reduce_addr_t& ad, int64_t i_out) {
221
319
  if (FLAT || ad.in_out_flat) return i_out * ad.in_out_step;
320
+ if (!FLAT && ad.in_out_div > 0) return (i_out / ad.in_out_div) * ad.in_out_step;
222
321
  if (ad.split < 0) return 0;
223
322
  return axes_offset(in, in_indexer, 0, ad.split, i_out);
224
323
  }
@@ -226,7 +325,8 @@ __device__ static __forceinline__ ssize_t reduce_in_out_offset(const cumo_na_iar
226
325
  // reduce_in_out_offset for the two operands of a zip reduction at once.
227
326
  template <bool FLAT>
228
327
  __device__ static __forceinline__ void reduce_in_out_offset_pair(const cumo_na_iarray_t& in, const cumo_na_iarray_t& in2, const cumo_na_indexer_t& in_indexer, const cumo_reduce_addr_t& ad, const cumo_reduce_addr_t& ad2, int64_t i_out, ssize_t* off, ssize_t* off2) {
229
- if (!FLAT && !ad.in_out_flat && !ad2.in_out_flat && ad.split >= 0 && ad.split == ad2.split) {
328
+ if (!FLAT && !ad.in_out_flat && !ad2.in_out_flat && ad.in_out_div == 0 && ad2.in_out_div == 0 &&
329
+ ad.split >= 0 && ad.split == ad2.split) {
230
330
  axes_offset_pair(in, in2, in_indexer, 0, ad.split, i_out, off, off2);
231
331
  return;
232
332
  }
@@ -237,6 +337,7 @@ __device__ static __forceinline__ void reduce_in_out_offset_pair(const cumo_na_i
237
337
  template <bool FLAT>
238
338
  __device__ static __forceinline__ ssize_t reduce_in_offset(const cumo_na_iarray_t& in, const cumo_na_indexer_t& in_indexer, const cumo_reduce_addr_t& ad, ssize_t in_out_off, int64_t i_reduce, int64_t i_in) {
239
339
  if (FLAT || ad.in_reduce_flat) return in_out_off + i_reduce * ad.in_reduce_step;
340
+ if (!FLAT && ad.in_reduce_div > 0) return in_out_off + (i_reduce / ad.in_reduce_div) * ad.in_reduce_step;
240
341
  if (ad.split < 0) return axes_offset(in, in_indexer, 0, in_indexer.ndim, i_in);
241
342
  return in_out_off + axes_offset(in, in_indexer, ad.split, in_indexer.ndim, i_reduce);
242
343
  }
@@ -340,7 +441,7 @@ __device__ static __forceinline__ auto reduce_axis(const cumo_na_iarray_t& in, c
340
441
  // once, which is what mulsum wants: the product it accumulates never exists as
341
442
  // an array. The pair comes out of one broadcast, so arg.in_indexer addresses
342
443
  // both and only the steps differ, which is what in2 and ad2 carry.
343
- template <bool FLAT, typename TypeIn, typename ReductionImpl>
444
+ template <bool FLAT, typename TypeIn, typename TypeIn2, typename ReductionImpl>
344
445
  __device__ static __forceinline__ auto reduce_axis_zip(const cumo_na_reduction_arg_t& arg, const cumo_na_iarray_t& in2,
345
446
  const cumo_reduce_addr_t& ad, const cumo_reduce_addr_t& ad2, ReductionImpl& impl,
346
447
  ssize_t in_out_off, ssize_t in_out_off2, int64_t i_in, int64_t begin, int64_t end,
@@ -356,7 +457,7 @@ __device__ static __forceinline__ auto reduce_axis_zip(const cumo_na_reduction_a
356
457
  TypeReduce accum = impl.Identity(0);
357
458
 
358
459
  for (; i_reduce < end; i_reduce += reduce_block_size, i_in += reduce_block_size) {
359
- impl.Reduce(impl.MapIn(*reinterpret_cast<TypeIn*>(p), *reinterpret_cast<TypeIn*>(q), i_reduce), accum);
460
+ impl.Reduce(impl.MapIn(*reinterpret_cast<TypeIn*>(p), *reinterpret_cast<TypeIn2*>(q), i_reduce), accum);
360
461
  p = (FLAT || ad.in_reduce_flat)
361
462
  ? p + advance
362
463
  : arg.in.ptr + reduce_in_offset<FLAT>(arg.in, arg.in_indexer, ad, in_out_off, i_reduce + reduce_block_size, i_in + reduce_block_size);
@@ -402,7 +503,7 @@ __global__ static void reduction_kernel(CUMO_GRID_CONSTANT cumo_na_reduction_arg
402
503
 
403
504
  // Variant of reduction_kernel reading two inputs, for mulsum. See
404
505
  // reduce_axis_zip above.
405
- template <bool FLAT, typename TypeIn, typename TypeOut, typename ReductionImpl>
506
+ template <bool FLAT, typename TypeIn, typename TypeIn2, typename TypeOut, typename ReductionImpl>
406
507
  __global__ static void reduction_zip_kernel(CUMO_GRID_CONSTANT cumo_na_reduction_arg_t arg, CUMO_GRID_CONSTANT cumo_na_iarray_t in2, CUMO_GRID_CONSTANT cumo_reduce_addr_t ad, CUMO_GRID_CONSTANT cumo_reduce_addr_t ad2, int out_block_size, int reduce_block_size, ReductionImpl impl) {
407
508
  using TypeReduce = decltype(impl.Identity(0));
408
509
 
@@ -423,7 +524,7 @@ __global__ static void reduction_zip_kernel(CUMO_GRID_CONSTANT cumo_na_reduction
423
524
  reduce_in_out_offset_pair<FLAT>(arg.in, in2, arg.in_indexer, ad, ad2, i_out, &in_out_off, &in_out_off2);
424
525
  int64_t i_in = i_out * reduce_total_size + reduce_offset;
425
526
 
426
- TypeReduce accum = reduce_axis_zip<FLAT,TypeIn>(arg, in2, ad, ad2, impl, in_out_off, in_out_off2, i_in, 0, reduce_total_size, reduce_offset, reduce_block_size);
527
+ TypeReduce accum = reduce_axis_zip<FLAT,TypeIn,TypeIn2>(arg, in2, ad, ad2, impl, in_out_off, in_out_off2, i_in, 0, reduce_total_size, reduce_offset, reduce_block_size);
427
528
 
428
529
  accum = reduce_in_block(accum, sdata, tid, out_block_size, reduce_block_size, !ad.out_inner, impl);
429
530
  if (reduce_offset == 0) {
@@ -433,6 +534,89 @@ __global__ static void reduction_zip_kernel(CUMO_GRID_CONSTANT cumo_na_reduction
433
534
  }
434
535
  }
435
536
 
537
+ // The reduce axis of a zip whose operands both address without the indexer.
538
+ // Splitting this out of reduce_axis_zip is what buys the speed: the general
539
+ // path is gone from the instantiation, so the indexer, which carries shape[]
540
+ // for CUMO_NA_MAX_DIMENSION, never has to be live.
541
+ template <typename TypeIn, typename TypeIn2, typename ReductionImpl>
542
+ __device__ static __forceinline__ auto reduce_axis_zip_nodim(const cumo_na_reduction_arg_t& arg, const cumo_na_iarray_t& in2,
543
+ const cumo_reduce_addr_t& ad, const cumo_reduce_addr_t& ad2, ReductionImpl& impl,
544
+ ssize_t in_out_off, ssize_t in_out_off2, int64_t begin, int64_t end,
545
+ int64_t reduce_offset, int64_t reduce_block_size) -> decltype(impl.Identity(0)) {
546
+ using TypeReduce = decltype(impl.Identity(0));
547
+
548
+ int64_t i_reduce = begin + reduce_offset;
549
+ char* p = arg.in.ptr + in_out_off + i_reduce * ad.in_reduce_step;
550
+ ssize_t advance = ad.in_reduce_step * reduce_block_size;
551
+
552
+ TypeReduce accum = impl.Identity(0);
553
+
554
+ // The two loops keep the divisor out of the body. One loop with a divisor
555
+ // of 1 for the flat case costs a 64-bit division on every element, which
556
+ // is more than the addressing it saves.
557
+ if (ad2.in_reduce_flat) {
558
+ char* q = in2.ptr + in_out_off2 + i_reduce * ad2.in_reduce_step;
559
+ ssize_t advance2 = ad2.in_reduce_step * reduce_block_size;
560
+
561
+ for (; i_reduce < end; i_reduce += reduce_block_size, p += advance, q += advance2) {
562
+ impl.Reduce(impl.MapIn(*reinterpret_cast<TypeIn*>(p), *reinterpret_cast<TypeIn2*>(q), i_reduce), accum);
563
+ }
564
+ } else {
565
+ int64_t div2 = ad2.in_reduce_div;
566
+
567
+ for (; i_reduce < end; i_reduce += reduce_block_size, p += advance) {
568
+ char* q = in2.ptr + in_out_off2 + (i_reduce / div2) * ad2.in_reduce_step;
569
+ impl.Reduce(impl.MapIn(*reinterpret_cast<TypeIn*>(p), *reinterpret_cast<TypeIn2*>(q), i_reduce), accum);
570
+ }
571
+ }
572
+ return accum;
573
+ }
574
+
575
+ // reduction_zip_kernel for the same case.
576
+ template <typename TypeIn, typename TypeIn2, typename TypeOut, typename ReductionImpl>
577
+ __global__ static void reduction_zip_nodim_kernel(CUMO_GRID_CONSTANT cumo_na_reduction_arg_t arg, CUMO_GRID_CONSTANT cumo_na_iarray_t in2, CUMO_GRID_CONSTANT cumo_reduce_addr_t ad, CUMO_GRID_CONSTANT cumo_reduce_addr_t ad2, int out_block_size, int reduce_block_size, ReductionImpl impl) {
578
+ using TypeReduce = decltype(impl.Identity(0));
579
+
580
+ extern __shared__ __align__(8) char sdata_raw[];
581
+ TypeReduce* sdata = reinterpret_cast<TypeReduce*>(sdata_raw);
582
+ unsigned int tid = threadIdx.x;
583
+
584
+ int64_t out_total_size = arg.out_indexer.total_size;
585
+ int64_t reduce_total_size = arg.in_indexer.total_size / out_total_size;
586
+ int64_t out_div2 = ad2.in_out_flat ? 1 : ad2.in_out_div;
587
+
588
+ int64_t reduce_offset, out_offset;
589
+ reduce_thread_split(ad, tid, out_block_size, reduce_block_size, &reduce_offset, &out_offset);
590
+ int64_t out_base = blockIdx.x * out_block_size;
591
+ int64_t out_stride = gridDim.x * out_block_size;
592
+
593
+ for (int64_t i_out = out_base + out_offset; i_out < out_total_size; i_out += out_stride) {
594
+ ssize_t in_out_off = i_out * ad.in_out_step;
595
+ ssize_t in_out_off2 = (i_out / out_div2) * ad2.in_out_step;
596
+
597
+ TypeReduce accum = reduce_axis_zip_nodim<TypeIn,TypeIn2>(arg, in2, ad, ad2, impl, in_out_off, in_out_off2, 0, reduce_total_size, reduce_offset, reduce_block_size);
598
+
599
+ accum = reduce_in_block(accum, sdata, tid, out_block_size, reduce_block_size, !ad.out_inner, impl);
600
+ if (reduce_offset == 0) {
601
+ TypeOut* out_ptr = reinterpret_cast<TypeOut*>(arg.out.ptr + i_out * ad.out_step);
602
+ *out_ptr = impl.MapOut(accum);
603
+ }
604
+ }
605
+ }
606
+
607
+ // Whether both operands of a zip address without the indexer: one is flat, the
608
+ // other flat or flat once its broadcast axes are divided out.
609
+ //
610
+ // Only the second operand may carry a divisor. The first is the receiver, and
611
+ // a broadcast one reaches this as the argument: a.mulsum(b) with b the smaller
612
+ // shape. Writing it the other way round leaves the general path, which answers
613
+ // the same and takes the time the kernel below saves.
614
+ static inline bool zip_axes_need_no_dim(const cumo_reduce_addr_t& ad, const cumo_reduce_addr_t& ad2) {
615
+ return ad.in_out_flat && ad.in_reduce_flat && ad.out_flat &&
616
+ (ad2.in_out_flat || ad2.in_out_div > 0) &&
617
+ (ad2.in_reduce_flat || ad2.in_reduce_div > 0);
618
+ }
619
+
436
620
  // Variant of reduction_kernel for arg-reductions (argmax/argmin), which report
437
621
  // the index along the reduction axis rather than the index of an element.
438
622
  template <bool FLAT, typename TypeIn, typename TypeOut, typename ReductionImpl>
@@ -539,8 +723,45 @@ __global__ static void reduction_partial_kernel(CUMO_GRID_CONSTANT cumo_na_reduc
539
723
  }
540
724
  }
541
725
 
726
+ // reduction_zip_partial_kernel for operands that address without the indexer.
727
+ // A split reduction is what a small output over a long reduce axis takes, so
728
+ // leaving this one on the general path would miss the shape that gains most.
729
+ template <typename TypeIn, typename TypeIn2, typename TypeReduce, typename ReductionImpl>
730
+ __global__ static void reduction_zip_nodim_partial_kernel(CUMO_GRID_CONSTANT cumo_na_reduction_arg_t arg, CUMO_GRID_CONSTANT cumo_na_iarray_t in2, CUMO_GRID_CONSTANT cumo_reduce_addr_t ad, CUMO_GRID_CONSTANT cumo_reduce_addr_t ad2, TypeReduce* partial, int64_t n_split, int64_t chunk, int out_block_size, int reduce_block_size, ReductionImpl impl) {
731
+ extern __shared__ __align__(8) char sdata_raw[];
732
+ TypeReduce* sdata = reinterpret_cast<TypeReduce*>(sdata_raw);
733
+ unsigned int tid = threadIdx.x;
734
+
735
+ int64_t out_total_size = arg.out_indexer.total_size;
736
+ int64_t reduce_total_size = arg.in_indexer.total_size / out_total_size;
737
+ int64_t partial_total_size = out_total_size * n_split;
738
+ int64_t out_div2 = ad2.in_out_flat ? 1 : ad2.in_out_div;
739
+
740
+ int64_t reduce_offset, out_offset;
741
+ reduce_thread_split(ad, tid, out_block_size, reduce_block_size, &reduce_offset, &out_offset);
742
+ int64_t out_base = blockIdx.x * out_block_size;
743
+ int64_t out_stride = gridDim.x * out_block_size;
744
+
745
+ for (int64_t i = out_base + out_offset; i < partial_total_size; i += out_stride) {
746
+ int64_t i_out = i % out_total_size;
747
+ int64_t i_split = i / out_total_size;
748
+ int64_t begin = i_split * chunk;
749
+ int64_t end = begin + chunk;
750
+ if (end > reduce_total_size) end = reduce_total_size;
751
+ ssize_t in_out_off = i_out * ad.in_out_step;
752
+ ssize_t in_out_off2 = (i_out / out_div2) * ad2.in_out_step;
753
+
754
+ TypeReduce accum = reduce_axis_zip_nodim<TypeIn,TypeIn2>(arg, in2, ad, ad2, impl, in_out_off, in_out_off2, begin, end, reduce_offset, reduce_block_size);
755
+
756
+ accum = reduce_in_block(accum, sdata, tid, out_block_size, reduce_block_size, !ad.out_inner, impl);
757
+ if (reduce_offset == 0) {
758
+ partial[i_out * n_split + i_split] = accum;
759
+ }
760
+ }
761
+ }
762
+
542
763
  // First pass of a split zip reduction. See reduction_partial_kernel above.
543
- template <bool FLAT, typename TypeIn, typename TypeReduce, typename ReductionImpl>
764
+ template <bool FLAT, typename TypeIn, typename TypeIn2, typename TypeReduce, typename ReductionImpl>
544
765
  __global__ static void reduction_zip_partial_kernel(CUMO_GRID_CONSTANT cumo_na_reduction_arg_t arg, CUMO_GRID_CONSTANT cumo_na_iarray_t in2, CUMO_GRID_CONSTANT cumo_reduce_addr_t ad, CUMO_GRID_CONSTANT cumo_reduce_addr_t ad2, TypeReduce* partial, int64_t n_split, int64_t chunk, int out_block_size, int reduce_block_size, ReductionImpl impl) {
545
766
  extern __shared__ __align__(8) char sdata_raw[];
546
767
  TypeReduce* sdata = reinterpret_cast<TypeReduce*>(sdata_raw);
@@ -565,7 +786,7 @@ __global__ static void reduction_zip_partial_kernel(CUMO_GRID_CONSTANT cumo_na_r
565
786
  reduce_in_out_offset_pair<FLAT>(arg.in, in2, arg.in_indexer, ad, ad2, i_out, &in_out_off, &in_out_off2);
566
787
  int64_t i_in = i_out * reduce_total_size + begin + reduce_offset;
567
788
 
568
- TypeReduce accum = reduce_axis_zip<FLAT,TypeIn>(arg, in2, ad, ad2, impl, in_out_off, in_out_off2, i_in, begin, end, reduce_offset, reduce_block_size);
789
+ TypeReduce accum = reduce_axis_zip<FLAT,TypeIn,TypeIn2>(arg, in2, ad, ad2, impl, in_out_off, in_out_off2, i_in, begin, end, reduce_offset, reduce_block_size);
569
790
 
570
791
  accum = reduce_in_block(accum, sdata, tid, out_block_size, reduce_block_size, !ad.out_inner, impl);
571
792
  if (reduce_offset == 0) {
@@ -602,7 +823,7 @@ struct reduce_combine {
602
823
  // the partials so the caller can combine them with its own second pass. The
603
824
  // caller frees the returned buffer.
604
825
  template <typename TypeIn, typename TypeReduce, typename ReductionImpl, bool ARG = false>
605
- TypeReduce* reduce_partial_pass(cumo_na_reduction_arg_t arg, cumo_reduce_addr_t ad, int64_t n_split, int64_t reduce_total_size, cumo_na_reduction_arg_t* arg2, ReductionImpl& impl) {
826
+ TypeReduce* reduce_partial_pass(cumo_na_reduction_arg_t arg, cumo_reduce_addr_t ad, int64_t n_split, int64_t reduce_total_size, cumo_na_reduction_arg_t* arg2, ReductionImpl& impl, char* held = 0) {
606
827
  int64_t chunk = (reduce_total_size + n_split - 1) / n_split;
607
828
  int64_t partial_total_size = arg.out_indexer.total_size * n_split;
608
829
  TypeReduce* partial = reinterpret_cast<TypeReduce*>(cumo_cuda_runtime_malloc(sizeof(TypeReduce) * partial_total_size));
@@ -618,7 +839,7 @@ TypeReduce* reduce_partial_pass(cumo_na_reduction_arg_t arg, cumo_reduce_addr_t
618
839
  } else {
619
840
  reduction_partial_kernel<false,ARG,TypeIn,TypeReduce,ReductionImpl><<<grid_size, max_block_size, shared_mem_size>>>(arg, ad, partial, n_split, chunk, out_block_size, reduce_block_size, impl);
620
841
  }
621
- cumo_cuda_runtime_check_kernel_launch();
842
+ cumo_check_launch_holding(partial, held);
622
843
 
623
844
  arg2->in.ptr = reinterpret_cast<char*>(partial);
624
845
  arg2->in.step[0] = sizeof(TypeReduce);
@@ -635,8 +856,8 @@ static inline bool zip_axes_are_flat(const cumo_reduce_addr_t& ad, const cumo_re
635
856
  }
636
857
 
637
858
  // First pass of a split zip reduction. See reduce_partial_pass above.
638
- template <typename TypeIn, typename TypeReduce, typename ReductionImpl>
639
- TypeReduce* reduce_zip_partial_pass(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, cumo_reduce_addr_t ad, cumo_reduce_addr_t ad2, int64_t n_split, int64_t reduce_total_size, cumo_na_reduction_arg_t* arg2, ReductionImpl& impl) {
859
+ template <typename TypeIn, typename TypeIn2, typename TypeReduce, typename ReductionImpl>
860
+ TypeReduce* reduce_zip_partial_pass(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, cumo_reduce_addr_t ad, cumo_reduce_addr_t ad2, int64_t n_split, int64_t reduce_total_size, cumo_na_reduction_arg_t* arg2, ReductionImpl& impl, char* held = 0) {
640
861
  int64_t chunk = (reduce_total_size + n_split - 1) / n_split;
641
862
  int64_t partial_total_size = arg.out_indexer.total_size * n_split;
642
863
  TypeReduce* partial = reinterpret_cast<TypeReduce*>(cumo_cuda_runtime_malloc(sizeof(TypeReduce) * partial_total_size));
@@ -648,11 +869,13 @@ TypeReduce* reduce_zip_partial_pass(cumo_na_reduction_arg_t arg, cumo_na_iarray_
648
869
  int64_t shared_mem_size = sizeof(TypeReduce) * max_block_size;
649
870
 
650
871
  if (zip_axes_are_flat(ad, ad2)) {
651
- reduction_zip_partial_kernel<true,TypeIn,TypeReduce,ReductionImpl><<<grid_size, max_block_size, shared_mem_size>>>(arg, in2, ad, ad2, partial, n_split, chunk, out_block_size, reduce_block_size, impl);
872
+ reduction_zip_partial_kernel<true,TypeIn,TypeIn2,TypeReduce,ReductionImpl><<<grid_size, max_block_size, shared_mem_size>>>(arg, in2, ad, ad2, partial, n_split, chunk, out_block_size, reduce_block_size, impl);
873
+ } else if (zip_axes_need_no_dim(ad, ad2)) {
874
+ reduction_zip_nodim_partial_kernel<TypeIn,TypeIn2,TypeReduce,ReductionImpl><<<grid_size, max_block_size, shared_mem_size>>>(arg, in2, ad, ad2, partial, n_split, chunk, out_block_size, reduce_block_size, impl);
652
875
  } else {
653
- reduction_zip_partial_kernel<false,TypeIn,TypeReduce,ReductionImpl><<<grid_size, max_block_size, shared_mem_size>>>(arg, in2, ad, ad2, partial, n_split, chunk, out_block_size, reduce_block_size, impl);
876
+ reduction_zip_partial_kernel<false,TypeIn,TypeIn2,TypeReduce,ReductionImpl><<<grid_size, max_block_size, shared_mem_size>>>(arg, in2, ad, ad2, partial, n_split, chunk, out_block_size, reduce_block_size, impl);
654
877
  }
655
- cumo_cuda_runtime_check_kernel_launch();
878
+ cumo_check_launch_holding(partial, held);
656
879
 
657
880
  arg2->in.ptr = reinterpret_cast<char*>(partial);
658
881
  arg2->in.step[0] = sizeof(TypeReduce);
@@ -664,8 +887,12 @@ TypeReduce* reduce_zip_partial_pass(cumo_na_reduction_arg_t arg, cumo_na_iarray_
664
887
 
665
888
  } // cumo_detail
666
889
 
890
+ // held0 and held1 are every scratch buffer still outstanding, the caller's
891
+ // included. The checks free them before they raise, since rb_raise is a longjmp
892
+ // and nothing written after it runs. The split forms pass their own partial as
893
+ // well as whatever they were handed.
667
894
  template <typename TypeIn, typename TypeOut, typename ReductionImpl>
668
- void cumo_reduce(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
895
+ void cumo_reduce(cumo_na_reduction_arg_t arg, ReductionImpl&& impl, char* held0 = 0, char* held1 = 0) {
669
896
  if (arg.out_indexer.total_size == 0) {
670
897
  return;
671
898
  }
@@ -686,7 +913,7 @@ void cumo_reduce(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
686
913
  } else {
687
914
  cumo_detail::reduction_kernel<false,TypeIn,TypeOut,ReductionImpl><<<grid_size, block_size, shared_mem_size>>>(arg, ad, out_block_size, reduce_block_size, impl);
688
915
  }
689
- cumo_cuda_runtime_check_kernel_launch();
916
+ cumo_check_launch_holding(held0, held1);
690
917
  }
691
918
 
692
919
  // Runs the reduce axis of one output across several blocks and combines their
@@ -695,7 +922,7 @@ void cumo_reduce(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
695
922
  // case. Only for an impl whose Identity ignores its index argument — see
696
923
  // reduce_combine above.
697
924
  template <typename TypeIn, typename TypeOut, typename ReductionImpl>
698
- void cumo_reduce_split(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
925
+ void cumo_reduce_split(cumo_na_reduction_arg_t arg, ReductionImpl&& impl, char* held = 0) {
699
926
  using TypeReduce = decltype(impl.Identity(0));
700
927
 
701
928
  if (arg.out_indexer.total_size == 0) {
@@ -711,20 +938,20 @@ void cumo_reduce_split(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
711
938
 
712
939
  int64_t n_split = cumo_detail::reduce_split_count(reduce_total_size, out_block_num);
713
940
  if (n_split < 2) {
714
- cumo_reduce<TypeIn, TypeOut, ReductionImpl>(arg, std::forward<ReductionImpl>(impl));
941
+ cumo_reduce<TypeIn, TypeOut, ReductionImpl>(arg, std::forward<ReductionImpl>(impl), held);
715
942
  return;
716
943
  }
717
944
 
718
945
  cumo_na_reduction_arg_t arg2 = arg;
719
- TypeReduce* partial = cumo_detail::reduce_partial_pass<TypeIn, TypeReduce, ReductionImpl>(arg, ad, n_split, reduce_total_size, &arg2, impl);
720
- cumo_reduce<TypeReduce, TypeOut, cumo_detail::reduce_combine<ReductionImpl>>(arg2, cumo_detail::reduce_combine<ReductionImpl>{impl});
946
+ TypeReduce* partial = cumo_detail::reduce_partial_pass<TypeIn, TypeReduce, ReductionImpl>(arg, ad, n_split, reduce_total_size, &arg2, impl, held);
947
+ cumo_reduce<TypeReduce, TypeOut, cumo_detail::reduce_combine<ReductionImpl>>(arg2, cumo_detail::reduce_combine<ReductionImpl>{impl}, reinterpret_cast<char*>(partial), held);
721
948
  cumo_cuda_runtime_free(reinterpret_cast<char*>(partial));
722
949
  }
723
950
 
724
951
  // Variant of cumo_reduce reading two inputs, for mulsum. in2 describes the same
725
952
  // shape as arg.in, since the one in_indexer addresses both.
726
- template <typename TypeIn, typename TypeOut, typename ReductionImpl>
727
- void cumo_reduce_zip(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, ReductionImpl&& impl) {
953
+ template <typename TypeIn, typename TypeIn2, typename TypeOut, typename ReductionImpl>
954
+ void cumo_reduce_zip(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, ReductionImpl&& impl, char* held0 = 0, char* held1 = 0) {
728
955
  if (arg.out_indexer.total_size == 0) {
729
956
  return;
730
957
  }
@@ -732,8 +959,8 @@ void cumo_reduce_zip(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, Reductio
732
959
  int64_t reduce_total_size = arg.in_indexer.total_size / arg.out_indexer.total_size;
733
960
  cumo_na_reduction_arg_t arg2 = arg;
734
961
  arg2.in = in2;
735
- cumo_detail::cumo_reduce_addr_t ad = cumo_detail::make_reduce_addr(arg, reduce_total_size);
736
- cumo_detail::cumo_reduce_addr_t ad2 = cumo_detail::make_reduce_addr(arg2, reduce_total_size);
962
+ cumo_detail::cumo_reduce_addr_t ad, ad2;
963
+ cumo_detail::make_zip_reduce_addrs(arg, arg2, reduce_total_size, &ad, &ad2);
737
964
 
738
965
  int64_t out_block_size, reduce_block_size;
739
966
  cumo_detail::reduce_block_split(ad, reduce_total_size, &out_block_size, &reduce_block_size);
@@ -744,17 +971,19 @@ void cumo_reduce_zip(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, Reductio
744
971
  int64_t shared_mem_size = sizeof(decltype(impl.Identity(0))) * block_size;
745
972
 
746
973
  if (cumo_detail::zip_axes_are_flat(ad, ad2) && ad.out_flat) {
747
- cumo_detail::reduction_zip_kernel<true,TypeIn,TypeOut,ReductionImpl><<<grid_size, block_size, shared_mem_size>>>(arg, in2, ad, ad2, out_block_size, reduce_block_size, impl);
974
+ cumo_detail::reduction_zip_kernel<true,TypeIn,TypeIn2,TypeOut,ReductionImpl><<<grid_size, block_size, shared_mem_size>>>(arg, in2, ad, ad2, out_block_size, reduce_block_size, impl);
975
+ } else if (cumo_detail::zip_axes_need_no_dim(ad, ad2)) {
976
+ cumo_detail::reduction_zip_nodim_kernel<TypeIn,TypeIn2,TypeOut,ReductionImpl><<<grid_size, block_size, shared_mem_size>>>(arg, in2, ad, ad2, out_block_size, reduce_block_size, impl);
748
977
  } else {
749
- cumo_detail::reduction_zip_kernel<false,TypeIn,TypeOut,ReductionImpl><<<grid_size, block_size, shared_mem_size>>>(arg, in2, ad, ad2, out_block_size, reduce_block_size, impl);
978
+ cumo_detail::reduction_zip_kernel<false,TypeIn,TypeIn2,TypeOut,ReductionImpl><<<grid_size, block_size, shared_mem_size>>>(arg, in2, ad, ad2, out_block_size, reduce_block_size, impl);
750
979
  }
751
- cumo_cuda_runtime_check_kernel_launch();
980
+ cumo_check_launch_holding(held0, held1);
752
981
  }
753
982
 
754
983
  // cumo_reduce_split for a zip reduction. The first pass reads both operands and
755
984
  // the combine pass has only accumulators left, so it is the plain one.
756
- template <typename TypeIn, typename TypeOut, typename ReductionImpl>
757
- void cumo_reduce_zip_split(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, ReductionImpl&& impl) {
985
+ template <typename TypeIn, typename TypeIn2, typename TypeOut, typename ReductionImpl>
986
+ void cumo_reduce_zip_split(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, ReductionImpl&& impl, char* held = 0) {
758
987
  using TypeReduce = decltype(impl.Identity(0));
759
988
 
760
989
  if (arg.out_indexer.total_size == 0) {
@@ -764,8 +993,8 @@ void cumo_reduce_zip_split(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, Re
764
993
  int64_t reduce_total_size = arg.in_indexer.total_size / arg.out_indexer.total_size;
765
994
  cumo_na_reduction_arg_t arg2 = arg;
766
995
  arg2.in = in2;
767
- cumo_detail::cumo_reduce_addr_t ad = cumo_detail::make_reduce_addr(arg, reduce_total_size);
768
- cumo_detail::cumo_reduce_addr_t ad2 = cumo_detail::make_reduce_addr(arg2, reduce_total_size);
996
+ cumo_detail::cumo_reduce_addr_t ad, ad2;
997
+ cumo_detail::make_zip_reduce_addrs(arg, arg2, reduce_total_size, &ad, &ad2);
769
998
 
770
999
  int64_t out_block_size, reduce_block_size;
771
1000
  cumo_detail::reduce_block_split(ad, reduce_total_size, &out_block_size, &reduce_block_size);
@@ -773,13 +1002,13 @@ void cumo_reduce_zip_split(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, Re
773
1002
 
774
1003
  int64_t n_split = cumo_detail::reduce_split_count(reduce_total_size, out_block_num);
775
1004
  if (n_split < 2) {
776
- cumo_reduce_zip<TypeIn, TypeOut, ReductionImpl>(arg, in2, std::forward<ReductionImpl>(impl));
1005
+ cumo_reduce_zip<TypeIn, TypeIn2, TypeOut, ReductionImpl>(arg, in2, std::forward<ReductionImpl>(impl), held);
777
1006
  return;
778
1007
  }
779
1008
 
780
1009
  cumo_na_reduction_arg_t combine = arg;
781
- TypeReduce* partial = cumo_detail::reduce_zip_partial_pass<TypeIn, TypeReduce, ReductionImpl>(arg, in2, ad, ad2, n_split, reduce_total_size, &combine, impl);
782
- cumo_reduce<TypeReduce, TypeOut, cumo_detail::reduce_combine<ReductionImpl>>(combine, cumo_detail::reduce_combine<ReductionImpl>{impl});
1010
+ TypeReduce* partial = cumo_detail::reduce_zip_partial_pass<TypeIn, TypeIn2, TypeReduce, ReductionImpl>(arg, in2, ad, ad2, n_split, reduce_total_size, &combine, impl, held);
1011
+ cumo_reduce<TypeReduce, TypeOut, cumo_detail::reduce_combine<ReductionImpl>>(combine, cumo_detail::reduce_combine<ReductionImpl>{impl}, reinterpret_cast<char*>(partial), held);
783
1012
  cumo_cuda_runtime_free(reinterpret_cast<char*>(partial));
784
1013
  }
785
1014
 
@@ -787,7 +1016,7 @@ void cumo_reduce_zip_split(cumo_na_reduction_arg_t arg, cumo_na_iarray_t in2, Re
787
1016
  // See reduction_pair_kernel above. out2 has to describe the same shape as
788
1017
  // arg.out, since the one out_indexer addresses both.
789
1018
  template <typename TypeIn, typename TypeOut, typename ReductionImpl>
790
- void cumo_reduce_pair(cumo_na_reduction_arg_t arg, cumo_na_iarray_t out2, ReductionImpl&& impl) {
1019
+ void cumo_reduce_pair(cumo_na_reduction_arg_t arg, cumo_na_iarray_t out2, ReductionImpl&& impl, char* held0 = 0, char* held1 = 0) {
791
1020
  if (arg.out_indexer.total_size == 0) {
792
1021
  return;
793
1022
  }
@@ -809,12 +1038,12 @@ void cumo_reduce_pair(cumo_na_reduction_arg_t arg, cumo_na_iarray_t out2, Reduct
809
1038
  } else {
810
1039
  cumo_detail::reduction_pair_kernel<false,TypeIn,TypeOut,ReductionImpl><<<grid_size, block_size, shared_mem_size>>>(arg, out2, ad, out_block_size, reduce_block_size, impl);
811
1040
  }
812
- cumo_cuda_runtime_check_kernel_launch();
1041
+ cumo_check_launch_holding(held0, held1);
813
1042
  }
814
1043
 
815
1044
  // cumo_reduce_split for the two-output form. Same constraint on Identity.
816
1045
  template <typename TypeIn, typename TypeOut, typename ReductionImpl>
817
- void cumo_reduce_pair_split(cumo_na_reduction_arg_t arg, cumo_na_iarray_t out2, ReductionImpl&& impl) {
1046
+ void cumo_reduce_pair_split(cumo_na_reduction_arg_t arg, cumo_na_iarray_t out2, ReductionImpl&& impl, char* held = 0) {
818
1047
  using TypeReduce = decltype(impl.Identity(0));
819
1048
 
820
1049
  if (arg.out_indexer.total_size == 0) {
@@ -830,20 +1059,20 @@ void cumo_reduce_pair_split(cumo_na_reduction_arg_t arg, cumo_na_iarray_t out2,
830
1059
 
831
1060
  int64_t n_split = cumo_detail::reduce_split_count(reduce_total_size, out_block_num);
832
1061
  if (n_split < 2) {
833
- cumo_reduce_pair<TypeIn, TypeOut, ReductionImpl>(arg, out2, std::forward<ReductionImpl>(impl));
1062
+ cumo_reduce_pair<TypeIn, TypeOut, ReductionImpl>(arg, out2, std::forward<ReductionImpl>(impl), held);
834
1063
  return;
835
1064
  }
836
1065
 
837
1066
  cumo_na_reduction_arg_t arg2 = arg;
838
- TypeReduce* partial = cumo_detail::reduce_partial_pass<TypeIn, TypeReduce, ReductionImpl>(arg, ad, n_split, reduce_total_size, &arg2, impl);
839
- cumo_reduce_pair<TypeReduce, TypeOut, cumo_detail::reduce_combine<ReductionImpl>>(arg2, out2, cumo_detail::reduce_combine<ReductionImpl>{impl});
1067
+ TypeReduce* partial = cumo_detail::reduce_partial_pass<TypeIn, TypeReduce, ReductionImpl>(arg, ad, n_split, reduce_total_size, &arg2, impl, held);
1068
+ cumo_reduce_pair<TypeReduce, TypeOut, cumo_detail::reduce_combine<ReductionImpl>>(arg2, out2, cumo_detail::reduce_combine<ReductionImpl>{impl}, reinterpret_cast<char*>(partial), held);
840
1069
  cumo_cuda_runtime_free(reinterpret_cast<char*>(partial));
841
1070
  }
842
1071
 
843
1072
  // Variant of cumo_reduce for arg-reductions (argmax/argmin), which returns
844
1073
  // indices along the reduction axis. See reduction_arg_kernel above.
845
1074
  template <typename TypeIn, typename TypeOut, typename ReductionImpl>
846
- void cumo_reduce_arg(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
1075
+ void cumo_reduce_arg(cumo_na_reduction_arg_t arg, ReductionImpl&& impl, char* held0 = 0, char* held1 = 0) {
847
1076
  if (arg.out_indexer.total_size == 0) {
848
1077
  return;
849
1078
  }
@@ -864,14 +1093,14 @@ void cumo_reduce_arg(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
864
1093
  } else {
865
1094
  cumo_detail::reduction_arg_kernel<false,TypeIn,TypeOut,ReductionImpl><<<grid_size, block_size, shared_mem_size>>>(arg, ad, out_block_size, reduce_block_size, impl);
866
1095
  }
867
- cumo_cuda_runtime_check_kernel_launch();
1096
+ cumo_check_launch_holding(held0, held1);
868
1097
  }
869
1098
 
870
1099
  // cumo_reduce_split for the arg form. The partials already carry indices along
871
1100
  // the reduce axis, so the second pass combines them with the plain kernel.
872
1101
  // Only for an impl whose Identity ignores its index argument.
873
1102
  template <typename TypeIn, typename TypeOut, typename ReductionImpl>
874
- void cumo_reduce_arg_split(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
1103
+ void cumo_reduce_arg_split(cumo_na_reduction_arg_t arg, ReductionImpl&& impl, char* held = 0) {
875
1104
  using TypeReduce = decltype(impl.Identity(0));
876
1105
 
877
1106
  if (arg.out_indexer.total_size == 0) {
@@ -887,13 +1116,13 @@ void cumo_reduce_arg_split(cumo_na_reduction_arg_t arg, ReductionImpl&& impl) {
887
1116
 
888
1117
  int64_t n_split = cumo_detail::reduce_split_count(reduce_total_size, out_block_num);
889
1118
  if (n_split < 2) {
890
- cumo_reduce_arg<TypeIn, TypeOut, ReductionImpl>(arg, std::forward<ReductionImpl>(impl));
1119
+ cumo_reduce_arg<TypeIn, TypeOut, ReductionImpl>(arg, std::forward<ReductionImpl>(impl), held);
891
1120
  return;
892
1121
  }
893
1122
 
894
1123
  cumo_na_reduction_arg_t arg2 = arg;
895
- TypeReduce* partial = cumo_detail::reduce_partial_pass<TypeIn, TypeReduce, ReductionImpl, true>(arg, ad, n_split, reduce_total_size, &arg2, impl);
896
- cumo_reduce<TypeReduce, TypeOut, cumo_detail::reduce_combine<ReductionImpl>>(arg2, cumo_detail::reduce_combine<ReductionImpl>{impl});
1124
+ TypeReduce* partial = cumo_detail::reduce_partial_pass<TypeIn, TypeReduce, ReductionImpl, true>(arg, ad, n_split, reduce_total_size, &arg2, impl, held);
1125
+ cumo_reduce<TypeReduce, TypeOut, cumo_detail::reduce_combine<ReductionImpl>>(arg2, cumo_detail::reduce_combine<ReductionImpl>{impl}, reinterpret_cast<char*>(partial), held);
897
1126
  cumo_cuda_runtime_free(reinterpret_cast<char*>(partial));
898
1127
  }
899
1128