cumo 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +590 -152
- data/CHANGELOG.md +29 -0
- data/README.md +140 -0
- data/bench/cumo_probe.rb +1 -1
- data/cumo.gemspec +1 -1
- data/ext/cumo/cuda/cudnn.c +18 -0
- data/ext/cumo/cuda/cudnn_impl.cpp +60 -14
- data/ext/cumo/cuda/driver.c +11 -9
- data/ext/cumo/cuda/runtime.c +56 -0
- data/ext/cumo/cumo.c +4 -0
- data/ext/cumo/extconf.rb +2 -0
- data/ext/cumo/include/cumo/bit_reduce_kernel.h +6 -6
- data/ext/cumo/include/cumo/cuda/cudnn.h +40 -0
- data/ext/cumo/include/cumo/cuda/runtime.h +2 -0
- data/ext/cumo/include/cumo/intern.h +9 -0
- data/ext/cumo/include/cumo/narray.h +1 -0
- data/ext/cumo/include/cumo/reduce_kernel.h +32 -28
- data/ext/cumo/include/cumo/row_kernel.h +170 -0
- data/ext/cumo/include/cumo/row_method.h +48 -0
- data/ext/cumo/include/cumo/template_kernel.h +26 -0
- data/ext/cumo/include/cumo/types/bf16_def.h +82 -0
- data/ext/cumo/include/cumo/types/bf16_def_kernel.h +28 -0
- data/ext/cumo/include/cumo/types/bf16_macro.h +186 -0
- data/ext/cumo/include/cumo/types/bf16_macro_kernel.h +213 -0
- data/ext/cumo/include/cumo/types/bfloat.h +22 -0
- data/ext/cumo/include/cumo/types/bfloat_kernel.h +16 -0
- data/ext/cumo/include/cumo/types/bit.h +2 -0
- data/ext/cumo/include/cumo/types/bit_kernel.h +2 -0
- data/ext/cumo/include/cumo/types/complex_macro.h +2 -0
- data/ext/cumo/include/cumo/types/complex_macro_kernel.h +2 -0
- data/ext/cumo/include/cumo/types/float_def.h +8 -0
- data/ext/cumo/include/cumo/types/float_def_kernel.h +8 -0
- data/ext/cumo/include/cumo/types/float_macro.h +19 -17
- data/ext/cumo/include/cumo/types/float_macro_kernel.h +19 -19
- data/ext/cumo/include/cumo/types/half_macro.h +15 -17
- data/ext/cumo/include/cumo/types/half_macro_kernel.h +15 -0
- data/ext/cumo/include/cumo/types/robj_macro.h +2 -0
- data/ext/cumo/include/cumo/types/xint_macro.h +2 -6
- data/ext/cumo/include/cumo/types/xint_macro_kernel.h +2 -0
- data/ext/cumo/include/cumo.h +2 -2
- data/ext/cumo/narray/gen/def/bfloat.rb +56 -0
- data/ext/cumo/narray/gen/def/bit.rb +11 -1
- data/ext/cumo/narray/gen/def/dcomplex.rb +11 -1
- data/ext/cumo/narray/gen/def/dfloat.rb +10 -1
- data/ext/cumo/narray/gen/def/hfloat.rb +10 -1
- data/ext/cumo/narray/gen/def/int16.rb +11 -1
- data/ext/cumo/narray/gen/def/int32.rb +11 -1
- data/ext/cumo/narray/gen/def/int64.rb +11 -1
- data/ext/cumo/narray/gen/def/int8.rb +11 -1
- data/ext/cumo/narray/gen/def/robject.rb +11 -1
- data/ext/cumo/narray/gen/def/scomplex.rb +11 -1
- data/ext/cumo/narray/gen/def/sfloat.rb +10 -1
- data/ext/cumo/narray/gen/def/uint16.rb +11 -1
- data/ext/cumo/narray/gen/def/uint32.rb +11 -1
- data/ext/cumo/narray/gen/def/uint64.rb +11 -1
- data/ext/cumo/narray/gen/def/uint8.rb +11 -1
- data/ext/cumo/narray/gen/narray_def.rb +0 -5
- data/ext/cumo/narray/gen/spec.rb +11 -14
- data/ext/cumo/narray/gen/tmpl/accum_arg.c +1 -1
- data/ext/cumo/narray/gen/tmpl/accum_binary_kernel.cu +9 -9
- data/ext/cumo/narray/gen/tmpl/accum_index.c +1 -1
- data/ext/cumo/narray/gen/tmpl/batch_norm.c +3 -3
- data/ext/cumo/narray/gen/tmpl/batch_norm_backward.c +3 -3
- data/ext/cumo/narray/gen/tmpl/complex_accum_kernel.cu +15 -5
- data/ext/cumo/narray/gen/tmpl/conv.c +88 -121
- data/ext/cumo/narray/gen/tmpl/conv_grad_w.c +89 -81
- data/ext/cumo/narray/gen/tmpl/conv_transpose.c +88 -121
- data/ext/cumo/narray/gen/tmpl/cum.c +8 -7
- data/ext/cumo/narray/gen/tmpl/cum_kernel.cu +7 -6
- data/ext/cumo/narray/gen/tmpl/fixed_batch_norm.c +3 -3
- data/ext/cumo/narray/gen/tmpl/float_accum_kernel.cu +22 -10
- data/ext/cumo/narray/gen/tmpl/gemm.c +11 -35
- data/ext/cumo/narray/gen/tmpl/layer_norm.c +91 -0
- data/ext/cumo/narray/gen/tmpl/layer_norm_kernel.cu +54 -0
- data/ext/cumo/narray/gen/tmpl/lib.c +1 -0
- data/ext/cumo/narray/gen/tmpl/lib_kernel.cu +1 -0
- data/ext/cumo/narray/gen/tmpl/median.c +1 -1
- data/ext/cumo/narray/gen/tmpl/poly.c +0 -3
- data/ext/cumo/narray/gen/tmpl/poly_kernel.cu +1 -1
- data/ext/cumo/narray/gen/tmpl/pooling_backward.c +2 -1
- data/ext/cumo/narray/gen/tmpl/pooling_forward.c +2 -1
- data/ext/cumo/narray/gen/tmpl/rand.c +9 -9
- data/ext/cumo/narray/gen/tmpl/rand_kernel.cu +9 -9
- data/ext/cumo/narray/gen/tmpl/rand_norm.c +4 -4
- data/ext/cumo/narray/gen/tmpl/rand_norm_kernel.cu +7 -7
- data/ext/cumo/narray/gen/tmpl/real_accum_kernel.cu +6 -6
- data/ext/cumo/narray/gen/tmpl/softmax.c +56 -0
- data/ext/cumo/narray/gen/tmpl/softmax_kernel.cu +157 -0
- data/ext/cumo/narray/gen/tmpl/sort.c +9 -32
- data/ext/cumo/narray/gen/tmpl/sort_index.c +16 -95
- data/ext/cumo/narray/gen/tmpl/store_array.c +12 -9
- data/ext/cumo/narray/gen/tmpl_bit/mask.c +11 -11
- data/ext/cumo/narray/gen/tmpl_bit/store_array.c +5 -9
- data/ext/cumo/narray/gen/tmpl_bit/where.c +50 -11
- data/ext/cumo/narray/gen/tmpl_bit/where2.c +11 -12
- data/ext/cumo/narray/gen/tmpl_bit/where_kernel.cu +1 -2
- data/ext/cumo/narray/index.c +2 -1
- data/ext/cumo/narray/math.c +2 -0
- data/ext/cumo/narray/narray.c +27 -1
- data/ext/cumo/narray/ndloop.c +73 -4
- data/ext/cumo/narray/sort_kernel.cu +61 -41
- data/ext/cumo/narray/struct.c +3 -0
- data/lib/cumo/cuda/cudnn.rb +1 -1
- data/lib/cumo/narray/extra.rb +2 -2
- data/test/bfloat_test.rb +238 -0
- data/test/cuda/memory_pool_test.rb +8 -7
- data/test/cuda/runtime_test.rb +25 -0
- data/test/cudnn_test.rb +108 -24
- data/test/extra_test.rb +1 -1
- data/test/fused_test.rb +311 -0
- data/test/math_test.rb +77 -2
- data/test/narray_alt_coverage_test.rb +1 -0
- data/test/narray_test.rb +70 -10
- data/test/test_helper.rb +1 -0
- metadata +20 -4
- data/ext/cumo/narray/gen/tmpl/qsort.c +0 -205
|
@@ -26,17 +26,79 @@ get_int_out_size(int* int_out_size, VALUE out_size, size_t ndim, size_t* x_shape
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
// Everything here holds a descriptor or the workspace, and the two allocations
|
|
30
|
+
// below raise on their own when the device is full.
|
|
31
|
+
static VALUE
|
|
32
|
+
<%=c_iter%>_run(VALUE v)
|
|
33
|
+
{
|
|
34
|
+
cumo_cuda_cudnn_conv_run_t *r = (cumo_cuda_cudnn_conv_run_t*)v;
|
|
35
|
+
cudnnDataType_t cudnn_dtype = <%= cudnn_dtype %>;
|
|
36
|
+
<%=cudnn_scalar_t%> alpha = 1;
|
|
37
|
+
<%=cudnn_scalar_t%> beta = 0;
|
|
38
|
+
cudnnConvolutionBwdDataAlgoPerf_t perf_result;
|
|
39
|
+
size_t workspace_size;
|
|
40
|
+
|
|
41
|
+
r->status = cumo_cuda_cudnn_CreateTensorDescriptor(&r->held.x_desc, r->x_cont, cudnn_dtype);
|
|
42
|
+
if (r->status != CUDNN_STATUS_SUCCESS) return Qnil;
|
|
43
|
+
r->status = cumo_cuda_cudnn_CreateTensorDescriptor(&r->held.y_desc, r->y, cudnn_dtype);
|
|
44
|
+
if (r->status != CUDNN_STATUS_SUCCESS) return Qnil;
|
|
45
|
+
r->status = cumo_cuda_cudnn_CreateFilterDescriptor(&r->held.w_desc, r->w_cont, cudnn_dtype);
|
|
46
|
+
if (r->status != CUDNN_STATUS_SUCCESS) return Qnil;
|
|
47
|
+
r->status = cumo_cuda_cudnn_CreateConvolutionDescriptor(&r->held.conv_desc, r->ndim, r->int_stride, r->int_pad, <%=cudnn_compute_dtype%>, <%=cudnn_math_type%>);
|
|
48
|
+
if (r->status != CUDNN_STATUS_SUCCESS) return Qnil;
|
|
49
|
+
|
|
50
|
+
// auto tune
|
|
51
|
+
r->status = cumo_cuda_cudnn_FindConvolutionBackwardDataAlgorithm(
|
|
52
|
+
&perf_result,
|
|
53
|
+
r->handle,
|
|
54
|
+
r->held.w_desc,
|
|
55
|
+
r->w_cont,
|
|
56
|
+
r->held.x_desc,
|
|
57
|
+
r->x_cont,
|
|
58
|
+
r->held.conv_desc,
|
|
59
|
+
r->held.y_desc,
|
|
60
|
+
r->y,
|
|
61
|
+
cumo_cuda_cudnn_max_workspace_size(),
|
|
62
|
+
r->int_stride,
|
|
63
|
+
r->int_pad,
|
|
64
|
+
r->ndim,
|
|
65
|
+
cudnn_dtype);
|
|
66
|
+
if (r->status != CUDNN_STATUS_SUCCESS) return Qnil;
|
|
67
|
+
// The descriptor asked for a math type; use the one the search settled on.
|
|
68
|
+
r->status = cudnnSetConvolutionMathType(r->held.conv_desc, perf_result.mathType);
|
|
69
|
+
if (r->status != CUDNN_STATUS_SUCCESS) return Qnil;
|
|
70
|
+
|
|
71
|
+
// The search may look at algorithms needing up to max_workspace_size,
|
|
72
|
+
// but only the one it picked has to be paid for.
|
|
73
|
+
workspace_size = perf_result.memory;
|
|
74
|
+
if (workspace_size > 0) r->held.workspace = cumo_cuda_runtime_malloc(workspace_size);
|
|
75
|
+
r->status = cudnnConvolutionBackwardData(
|
|
76
|
+
r->handle,
|
|
77
|
+
(void*)&alpha,
|
|
78
|
+
r->held.w_desc,
|
|
79
|
+
(void*)r->w_cont_ptr,
|
|
80
|
+
r->held.x_desc,
|
|
81
|
+
(void*)r->x_cont_ptr,
|
|
82
|
+
r->held.conv_desc,
|
|
83
|
+
perf_result.algo,
|
|
84
|
+
(void*)r->held.workspace,
|
|
85
|
+
workspace_size,
|
|
86
|
+
(void*)&beta,
|
|
87
|
+
r->held.y_desc,
|
|
88
|
+
(void*)r->y_ptr);
|
|
89
|
+
if (r->status != CUDNN_STATUS_SUCCESS) return Qnil;
|
|
90
|
+
|
|
91
|
+
r->status = cumo_cuda_cudnn_AddBias(r, cudnn_dtype, &alpha);
|
|
92
|
+
return Qnil;
|
|
93
|
+
}
|
|
94
|
+
|
|
29
95
|
// cover_all=true is not supported with CUDNN
|
|
30
96
|
// dilation > 1 is not supported yet
|
|
31
97
|
// x.conv(w, b: nil, stride: 1, pad: 0, out_size: nil, y: nil)
|
|
32
98
|
static VALUE
|
|
33
99
|
<%=c_func(-1)%>(int argc, VALUE argv[], VALUE self)
|
|
34
100
|
{
|
|
35
|
-
|
|
36
|
-
cudnnStatus_t status = 0;
|
|
37
|
-
cudnnHandle_t handle = 0;
|
|
38
|
-
<%=cudnn_scalar_t%> alpha = 1;
|
|
39
|
-
<%=cudnn_scalar_t%> beta = 0;
|
|
101
|
+
cumo_cuda_cudnn_conv_run_t r;
|
|
40
102
|
|
|
41
103
|
VALUE x=self, w, b, stride, pad, out_size, y;
|
|
42
104
|
VALUE kw_hash = Qnil;
|
|
@@ -49,23 +111,11 @@ static VALUE
|
|
|
49
111
|
size_t out_channels, batch_size;
|
|
50
112
|
|
|
51
113
|
VALUE x_cont, w_cont;
|
|
52
|
-
cudnnTensorDescriptor_t x_desc = 0;
|
|
53
|
-
cudnnTensorDescriptor_t y_desc = 0;
|
|
54
|
-
cudnnTensorDescriptor_t b_desc = 0;
|
|
55
|
-
cudnnFilterDescriptor_t w_desc = 0;
|
|
56
|
-
cudnnConvolutionDescriptor_t conv_desc = 0;
|
|
57
|
-
char *x_cont_ptr, *w_cont_ptr, *y_ptr;
|
|
58
|
-
|
|
59
|
-
cudnnConvolutionBwdDataAlgoPerf_t perf_result;
|
|
60
|
-
cudnnConvolutionBwdDataAlgo_t algo;
|
|
61
|
-
size_t max_workspace_size = cumo_cuda_cudnn_max_workspace_size();
|
|
62
|
-
size_t workspace_size;
|
|
63
|
-
char* workspace = 0;
|
|
64
|
-
|
|
65
114
|
int int_stride[CUMO_NA_MAX_DIMENSION];
|
|
66
115
|
int int_pad[CUMO_NA_MAX_DIMENSION];
|
|
67
116
|
int int_out_size[CUMO_NA_MAX_DIMENSION];
|
|
68
117
|
|
|
118
|
+
memset(&r, 0, sizeof(r));
|
|
69
119
|
rb_scan_args(argc, argv, "1:", &w, &kw_hash);
|
|
70
120
|
rb_get_kwargs(kw_hash, kw_table, 0, 5, opts);
|
|
71
121
|
b = cumo_option_value(opts[0], Qnil);
|
|
@@ -118,112 +168,29 @@ static VALUE
|
|
|
118
168
|
x_cont = cumo_na_as_contiguous_array(x);
|
|
119
169
|
w_cont = cumo_na_as_contiguous_array(w);
|
|
120
170
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
handle = cumo_cuda_cudnn_handle();
|
|
135
|
-
|
|
136
|
-
// auto tune
|
|
137
|
-
status = cumo_cuda_cudnn_FindConvolutionBackwardDataAlgorithm(
|
|
138
|
-
&perf_result,
|
|
139
|
-
handle,
|
|
140
|
-
w_desc,
|
|
141
|
-
w_cont,
|
|
142
|
-
x_desc,
|
|
143
|
-
x_cont,
|
|
144
|
-
conv_desc,
|
|
145
|
-
y_desc,
|
|
146
|
-
y,
|
|
147
|
-
max_workspace_size,
|
|
148
|
-
int_stride,
|
|
149
|
-
int_pad,
|
|
150
|
-
ndim,
|
|
151
|
-
cudnn_dtype);
|
|
152
|
-
if (status != CUDNN_STATUS_SUCCESS) goto CONV_TRANSPOSE_ERROR;
|
|
153
|
-
// The descriptor asked for a math type; use the one the search settled on.
|
|
154
|
-
status = cudnnSetConvolutionMathType(conv_desc, perf_result.mathType);
|
|
155
|
-
if (status != CUDNN_STATUS_SUCCESS) goto CONV_TRANSPOSE_ERROR;
|
|
156
|
-
|
|
157
|
-
algo = perf_result.algo;
|
|
158
|
-
workspace_size = perf_result.memory;
|
|
159
|
-
|
|
160
|
-
// The search may look at algorithms needing up to max_workspace_size,
|
|
161
|
-
// but only the one it picked has to be paid for.
|
|
162
|
-
if (workspace_size > 0) workspace = cumo_cuda_runtime_malloc(workspace_size);
|
|
163
|
-
status = cudnnConvolutionBackwardData(
|
|
164
|
-
handle,
|
|
165
|
-
(void*)&alpha,
|
|
166
|
-
w_desc,
|
|
167
|
-
(void*)w_cont_ptr,
|
|
168
|
-
x_desc,
|
|
169
|
-
(void*)x_cont_ptr,
|
|
170
|
-
conv_desc,
|
|
171
|
-
algo,
|
|
172
|
-
(void*)workspace,
|
|
173
|
-
workspace_size,
|
|
174
|
-
(void*)&beta,
|
|
175
|
-
y_desc,
|
|
176
|
-
(void*)y_ptr);
|
|
177
|
-
if (status != CUDNN_STATUS_SUCCESS) goto CONV_TRANSPOSE_ERROR;
|
|
178
|
-
|
|
171
|
+
r.x_cont = x_cont;
|
|
172
|
+
r.w_cont = w_cont;
|
|
173
|
+
r.y = y;
|
|
174
|
+
r.x_cont_ptr = cumo_na_get_offset_pointer_for_read(x_cont);
|
|
175
|
+
r.w_cont_ptr = cumo_na_get_offset_pointer_for_read(w_cont);
|
|
176
|
+
r.y_ptr = cumo_na_get_offset_pointer_for_write(y);
|
|
177
|
+
r.ndim = ndim;
|
|
178
|
+
r.int_stride = int_stride;
|
|
179
|
+
r.int_pad = int_pad;
|
|
180
|
+
|
|
181
|
+
// Taking the bias runs Ruby, which can raise, so it is settled before the
|
|
182
|
+
// ensure below has anything to give back.
|
|
179
183
|
if (b != Qnil) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
cumo_narray_t *nb, *nb_cont;
|
|
184
|
-
size_t *b_shape;
|
|
185
|
-
int b_ndim;
|
|
186
|
-
|
|
187
|
-
CUMO_CHECK_NARRAY_TYPE(b, cT);
|
|
188
|
-
CumoGetNArray(b, nb);
|
|
189
|
-
new_shape[0] = 1;
|
|
190
|
-
new_shape[1] = nb->size;
|
|
191
|
-
for (size_t i = 0; i < ndim; ++i) {
|
|
192
|
-
new_shape[i + 2] = 1;
|
|
193
|
-
}
|
|
194
|
-
b_cont = cumo_na_as_contiguous_array(b);
|
|
195
|
-
b_cont_ptr = cumo_na_get_offset_pointer_for_read(b_cont);
|
|
196
|
-
CumoGetNArray(b_cont, nb_cont);
|
|
197
|
-
b_shape = nb_cont->shape;
|
|
198
|
-
b_ndim = nb_cont->ndim;
|
|
199
|
-
// reshape b
|
|
200
|
-
nb_cont->ndim = ndim + 2;
|
|
201
|
-
nb_cont->shape = new_shape;
|
|
202
|
-
status = cumo_cuda_cudnn_CreateTensorDescriptor(&b_desc, b_cont, cudnn_dtype);
|
|
203
|
-
// restore b.shape
|
|
204
|
-
nb_cont->ndim = b_ndim;
|
|
205
|
-
nb_cont->shape = b_shape;
|
|
206
|
-
if (status != CUDNN_STATUS_SUCCESS) goto CONV_TRANSPOSE_ERROR;
|
|
207
|
-
|
|
208
|
-
status = cudnnAddTensor(
|
|
209
|
-
handle,
|
|
210
|
-
(void*)&alpha,
|
|
211
|
-
b_desc,
|
|
212
|
-
(void*)b_cont_ptr,
|
|
213
|
-
(void*)&alpha,
|
|
214
|
-
y_desc,
|
|
215
|
-
(void*)y_ptr);
|
|
216
|
-
if (status != CUDNN_STATUS_SUCCESS) goto CONV_TRANSPOSE_ERROR;
|
|
184
|
+
cumo_cuda_cudnn_check_input(b, cT, 1, &out_channels);
|
|
185
|
+
r.b_cont = cumo_na_as_contiguous_array(b);
|
|
186
|
+
r.b_cont_ptr = cumo_na_get_offset_pointer_for_read(r.b_cont);
|
|
217
187
|
}
|
|
188
|
+
r.handle = cumo_cuda_cudnn_handle();
|
|
218
189
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
if (w_desc) cudnnDestroyFilterDescriptor(w_desc);
|
|
224
|
-
if (conv_desc) cudnnDestroyConvolutionDescriptor(conv_desc);
|
|
225
|
-
if (workspace) cumo_cuda_runtime_free(workspace);
|
|
226
|
-
cumo_cuda_cudnn_check_status(status);
|
|
190
|
+
rb_ensure(<%=c_iter%>_run, (VALUE)&r, cumo_cuda_cudnn_release_conv_held, (VALUE)&r.held);
|
|
191
|
+
RB_GC_GUARD(r.b_cont);
|
|
192
|
+
cumo_cuda_cudnn_check_status(r.status);
|
|
193
|
+
cumo_cuda_runtime_check_status(r.held.wait_status);
|
|
227
194
|
|
|
228
195
|
return y;
|
|
229
196
|
}
|
|
@@ -28,24 +28,25 @@ static void
|
|
|
28
28
|
CUMO_SHOW_SYNCHRONIZE_FIXME_WARNING_ONCE("<%=name%><%=j%>", "<%=type_name%>");
|
|
29
29
|
cumo_cuda_runtime_device_synchronize();
|
|
30
30
|
|
|
31
|
-
<% if
|
|
32
|
-
// The scan this stands in for carries
|
|
33
|
-
//
|
|
31
|
+
<% if !acc_type.empty? && name == 'cumsum' %>
|
|
32
|
+
// The scan this stands in for carries the accumulator type, and a running
|
|
33
|
+
// sum in the element type stops moving once the partial outgrows it, so this
|
|
34
|
+
// path carries it too.
|
|
34
35
|
{
|
|
35
|
-
|
|
36
|
+
<%=acc_type%> acc, fy;
|
|
36
37
|
|
|
37
38
|
CUMO_GET_DATA_STRIDE(p1,s1,dtype,x);
|
|
38
|
-
acc =
|
|
39
|
+
acc = <%=to_acc%>(x);
|
|
39
40
|
CUMO_SET_DATA_STRIDE(p2,s2,dtype,x);
|
|
40
41
|
for (i--; i--;) {
|
|
41
42
|
CUMO_GET_DATA_STRIDE(p1,s1,dtype,y);
|
|
42
|
-
fy =
|
|
43
|
+
fy = <%=to_acc%>(y);
|
|
43
44
|
<% if j == '_nan' %>
|
|
44
45
|
if (acc != acc) { acc = fy; } else if (fy == fy) { acc += fy; }
|
|
45
46
|
<% else %>
|
|
46
47
|
acc += fy;
|
|
47
48
|
<% end %>
|
|
48
|
-
x =
|
|
49
|
+
x = <%=from_acc%>(acc);
|
|
49
50
|
CUMO_SET_DATA_STRIDE(p2,s2,dtype,x);
|
|
50
51
|
}
|
|
51
52
|
}
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
<% unless type_name == 'robject' %>
|
|
2
|
-
<% widen =
|
|
3
|
-
<% cum_t = widen ?
|
|
2
|
+
<% widen = !acc_type.empty? && name == 'cumsum' %>
|
|
3
|
+
<% cum_t = widen ? acc_type : 'dtype' %>
|
|
4
4
|
<% if widen %>
|
|
5
5
|
|
|
6
|
-
// A running sum
|
|
7
|
-
//
|
|
6
|
+
// A running sum in the element type stops moving once the partial outgrows it,
|
|
7
|
+
// so the scan carries the accumulator type and each element is rounded as it is
|
|
8
|
+
// written.
|
|
8
9
|
struct <%="cumo_thrust_#{name}_widen"%>
|
|
9
10
|
{
|
|
10
|
-
__host__ __device__
|
|
11
|
+
__host__ __device__ <%=acc_type%> operator()(dtype x) const { return <%=to_acc%>(x); }
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
struct <%="cumo_thrust_#{name}_narrow"%>
|
|
14
15
|
{
|
|
15
|
-
__host__ __device__ dtype operator()(
|
|
16
|
+
__host__ __device__ dtype operator()(<%=acc_type%> x) const { return <%=from_acc%>(x); }
|
|
16
17
|
};
|
|
17
18
|
<% end %>
|
|
18
19
|
<% (is_float ? ["","_nan"] : [""]).each do |j| %>
|
|
@@ -97,18 +97,18 @@ static VALUE
|
|
|
97
97
|
}
|
|
98
98
|
y_ptr = cumo_na_get_offset_pointer_for_write(y);
|
|
99
99
|
|
|
100
|
+
mode = cumo_cuda_cudnn_GetBatchNormMode(axis_ndim, int_axis);
|
|
101
|
+
handle = cumo_cuda_cudnn_handle();
|
|
102
|
+
|
|
100
103
|
status = cumo_cuda_cudnn_CreateTensorDescriptor(&x_desc, x_cont, cudnn_dtype);
|
|
101
104
|
if (status != CUDNN_STATUS_SUCCESS) goto FIXED_BATCH_NORM_ERROR;
|
|
102
105
|
|
|
103
|
-
mode = cumo_cuda_cudnn_GetBatchNormMode(axis_ndim, int_axis);
|
|
104
106
|
status = cumo_cuda_cudnn_CreateBNTensorDescriptor(&bn_desc, x_desc, mode);
|
|
105
107
|
if (status != CUDNN_STATUS_SUCCESS) goto FIXED_BATCH_NORM_ERROR;
|
|
106
108
|
// cuDNN derives the parameter descriptor as float for a half x and as x's
|
|
107
109
|
// own type otherwise, and the checks above hold every parameter to exactly
|
|
108
110
|
// that class, so none of them needs a cast.
|
|
109
111
|
|
|
110
|
-
handle = cumo_cuda_cudnn_handle();
|
|
111
|
-
|
|
112
112
|
status = cudnnBatchNormalizationForwardInference(
|
|
113
113
|
handle,
|
|
114
114
|
mode,
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
<%= load_erb('real_accum').result(binding) %>
|
|
2
|
-
<% acc =
|
|
3
|
-
<% to_acc = is_half ? 'cumo_half2float' : '' %>
|
|
4
|
-
<% from_acc = is_half ? 'cumo_float2half' : '' %>
|
|
2
|
+
<% acc = acc_type.empty? ? 'dtype' : acc_type %>
|
|
5
3
|
|
|
6
4
|
|
|
7
5
|
#if defined(__cplusplus)
|
|
@@ -27,19 +25,33 @@ struct cumo_<%=type_name%>_moments_impl {
|
|
|
27
25
|
__device__ void Reduce(Moments next, Moments& accum) {
|
|
28
26
|
if (next.n == 0) { return; }
|
|
29
27
|
if (accum.n == 0) { accum = next; return; }
|
|
28
|
+
// The two quotients share a denominator, and one reciprocal is much
|
|
29
|
+
// cheaper than two double divides on a GeForce card.
|
|
30
30
|
<%=acc%> n = accum.n + next.n;
|
|
31
|
+
<%=acc%> r = <%=acc%>(1) / n;
|
|
31
32
|
<%=acc%> delta = next.mean - accum.mean;
|
|
32
|
-
accum.mean = accum.mean + delta * (next.n
|
|
33
|
-
accum.m2 += next.m2 + delta * delta * accum.n * next.n
|
|
33
|
+
accum.mean = accum.mean + delta * (next.n * r);
|
|
34
|
+
accum.m2 += next.m2 + delta * delta * accum.n * next.n * r;
|
|
34
35
|
accum.n = n;
|
|
35
36
|
}
|
|
36
37
|
};
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
// An infinity has no deviation from a mean that is itself infinite, so a row
|
|
40
|
+
// holding one has a variance of NaN wherever the infinity falls in the tree.
|
|
41
|
+
// layer_norm shares the accumulator above and cannot use this, so it sits here
|
|
42
|
+
// rather than there.
|
|
43
|
+
struct cumo_<%=type_name%>_spread_impl : cumo_<%=type_name%>_moments_impl {
|
|
44
|
+
__device__ Moments MapIn(dtype in, int64_t /*index*/) {
|
|
45
|
+
<%=acc%> x = <%=to_acc%>(in);
|
|
46
|
+
return {1, x, isinf(x) ? <%=acc%>(NAN) : <%=acc%>(0)};
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
struct cumo_<%=type_name%>_var_impl : cumo_<%=type_name%>_spread_impl {
|
|
39
51
|
__device__ rtype MapOut(Moments accum) { return <%=from_acc%>(accum.m2 / (accum.n - 1)); }
|
|
40
52
|
};
|
|
41
53
|
|
|
42
|
-
struct cumo_<%=type_name%>_stddev_impl : cumo_<%=type_name%>
|
|
54
|
+
struct cumo_<%=type_name%>_stddev_impl : cumo_<%=type_name%>_spread_impl {
|
|
43
55
|
__device__ rtype MapOut(Moments accum) { return <%=from_acc%>(sqrt(accum.m2 / (accum.n - 1))); }
|
|
44
56
|
};
|
|
45
57
|
|
|
@@ -65,10 +77,10 @@ struct cumo_<%=type_name%>_rms_impl {
|
|
|
65
77
|
// Reduce above already returns early on a zero count, so the moments never see
|
|
66
78
|
// it, and mean and rms carry the count of what was left rather than the length
|
|
67
79
|
// of the reduce axis.
|
|
68
|
-
struct cumo_<%=type_name%>_moments_nan_impl : cumo_<%=type_name%>
|
|
69
|
-
__device__ Moments MapIn(dtype in, int64_t
|
|
80
|
+
struct cumo_<%=type_name%>_moments_nan_impl : cumo_<%=type_name%>_spread_impl {
|
|
81
|
+
__device__ Moments MapIn(dtype in, int64_t index) {
|
|
70
82
|
if (!not_nan(in)) { return {0, 0, 0}; }
|
|
71
|
-
return
|
|
83
|
+
return cumo_<%=type_name%>_spread_impl::MapIn(in, index);
|
|
72
84
|
}
|
|
73
85
|
};
|
|
74
86
|
|
|
@@ -1,34 +1,10 @@
|
|
|
1
1
|
<%
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
when 'scomplex'
|
|
9
|
-
'C'
|
|
10
|
-
when 'dcomplex'
|
|
11
|
-
'Z'
|
|
12
|
-
end
|
|
13
|
-
cutype =
|
|
14
|
-
case type_name
|
|
15
|
-
when 'hfloat'
|
|
16
|
-
'cumo_half'
|
|
17
|
-
when 'sfloat'
|
|
18
|
-
'float'
|
|
19
|
-
when 'dfloat'
|
|
20
|
-
'double'
|
|
21
|
-
when 'scomplex'
|
|
22
|
-
'cuComplex'
|
|
23
|
-
when 'dcomplex'
|
|
24
|
-
'cuDoubleComplex'
|
|
25
|
-
end
|
|
26
|
-
# cublasGemmStridedBatchedEx multiplies in half and accumulates in float, and
|
|
27
|
-
# takes its scalars in the accumulator's type.
|
|
28
|
-
scalar_t = is_half ? 'float' : 'dtype'
|
|
29
|
-
num_to_scalar = is_half ? '(float)NUM2DBL' : 'm_num_to_data'
|
|
30
|
-
scalar_one = is_half ? '1.0f' : 'm_one'
|
|
31
|
-
scalar_zero = is_half ? '0.0f' : 'm_zero'
|
|
2
|
+
# cublasGemmStridedBatchedEx multiplies in the element type and accumulates
|
|
3
|
+
# in the wider one, and takes its scalars in the accumulator's type.
|
|
4
|
+
scalar_t = acc_type.empty? ? 'dtype' : acc_type
|
|
5
|
+
num_to_scalar = acc_type.empty? ? 'm_num_to_data' : "(#{acc_type})NUM2DBL"
|
|
6
|
+
scalar_one = acc_one
|
|
7
|
+
scalar_zero = acc_zero
|
|
32
8
|
%>
|
|
33
9
|
|
|
34
10
|
#define ROW_SIZE(na) ((na)->shape[(na)->ndim-2])
|
|
@@ -298,7 +274,7 @@ static void
|
|
|
298
274
|
|
|
299
275
|
if (cumo_na_debug_flag) print_gemm_args(g, &a_layout, &b_layout, stridec, batch_count);
|
|
300
276
|
handle = cumo_cuda_cublas_handle();
|
|
301
|
-
<%
|
|
277
|
+
<% unless cublas_dtype.empty? %>
|
|
302
278
|
status = cublasGemmStridedBatchedEx(
|
|
303
279
|
handle,
|
|
304
280
|
b_layout.trans,
|
|
@@ -308,23 +284,23 @@ static void
|
|
|
308
284
|
g->k,
|
|
309
285
|
&g->alpha,
|
|
310
286
|
(const void*)(cumo_na_get_pointer_for_read(b_layout.a) + cumo_na_get_offset(b_layout.a)),
|
|
311
|
-
|
|
287
|
+
<%=cublas_dtype%>,
|
|
312
288
|
b_layout.ld,
|
|
313
289
|
b_layout.stride,
|
|
314
290
|
(const void*)(cumo_na_get_pointer_for_read(a_layout.a) + cumo_na_get_offset(a_layout.a)),
|
|
315
|
-
|
|
291
|
+
<%=cublas_dtype%>,
|
|
316
292
|
a_layout.ld,
|
|
317
293
|
a_layout.stride,
|
|
318
294
|
&g->beta,
|
|
319
295
|
(void*)(cumo_na_get_pointer_for_write(c) + cumo_na_get_offset(c)),
|
|
320
|
-
|
|
296
|
+
<%=cublas_dtype%>,
|
|
321
297
|
g->n,
|
|
322
298
|
stridec,
|
|
323
299
|
batch_count,
|
|
324
300
|
CUBLAS_COMPUTE_32F,
|
|
325
301
|
CUBLAS_GEMM_DEFAULT);
|
|
326
302
|
<% else %>
|
|
327
|
-
status = cublas<%=
|
|
303
|
+
status = cublas<%=cublas_prefix%>gemmStridedBatched(
|
|
328
304
|
handle,
|
|
329
305
|
b_layout.trans,
|
|
330
306
|
a_layout.trans,
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
void <%="cumo_#{c_iter}_kernel_launch"%>(char *px, char *pg, char *pb, char *py, uint64_t rows, uint64_t cols, double eps);
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Normalizes self along its last axis, then scales by gamma and shifts by beta.
|
|
5
|
+
Each row is taken on its own: y = (x - mean) / sqrt(var + eps) * gamma + beta,
|
|
6
|
+
where mean and var are of that row and var divides by the row length.
|
|
7
|
+
@overload layer_norm(gamma, beta, eps:1e-5)
|
|
8
|
+
@param [Cumo::<%=class_name%>] gamma scale, one-dimensional and as long as the last axis.
|
|
9
|
+
@param [Cumo::<%=class_name%>] beta shift, one-dimensional and as long as the last axis.
|
|
10
|
+
@param [Float] eps added to the variance before the square root.
|
|
11
|
+
Answers a new array: inplace! is not honoured.
|
|
12
|
+
@return [Cumo::<%=class_name%>] returns the normalized array, shaped like self.
|
|
13
|
+
*/
|
|
14
|
+
static VALUE
|
|
15
|
+
<%=c_func(-1)%>(int argc, VALUE argv[], VALUE self)
|
|
16
|
+
{
|
|
17
|
+
VALUE x=self, gamma, beta, eps, y;
|
|
18
|
+
VALUE kw_hash = Qnil;
|
|
19
|
+
ID kw_table[] = {rb_intern("eps")};
|
|
20
|
+
VALUE opts[] = {Qundef};
|
|
21
|
+
VALUE x_cont, gamma_cont, beta_cont;
|
|
22
|
+
cumo_narray_t *nx, *ngamma, *nbeta, *ny;
|
|
23
|
+
char *x_ptr, *gamma_ptr, *beta_ptr, *y_ptr;
|
|
24
|
+
size_t cols, rows;
|
|
25
|
+
double double_eps = 1e-5;
|
|
26
|
+
|
|
27
|
+
rb_scan_args(argc, argv, "2:", &gamma, &beta, &kw_hash);
|
|
28
|
+
rb_get_kwargs(kw_hash, kw_table, 0, 1, opts);
|
|
29
|
+
eps = cumo_option_value(opts[0], Qnil);
|
|
30
|
+
if (eps != Qnil) {
|
|
31
|
+
double_eps = NUM2DBL(eps);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
CUMO_CHECK_NARRAY_TYPE(x, cT);
|
|
35
|
+
CUMO_CHECK_NARRAY_TYPE(gamma, cT);
|
|
36
|
+
CUMO_CHECK_NARRAY_TYPE(beta, cT);
|
|
37
|
+
|
|
38
|
+
x_cont = cumo_na_as_contiguous_array(x);
|
|
39
|
+
gamma_cont = cumo_na_as_contiguous_array(gamma);
|
|
40
|
+
beta_cont = cumo_na_as_contiguous_array(beta);
|
|
41
|
+
|
|
42
|
+
CumoGetNArray(x_cont, nx);
|
|
43
|
+
if (nx->ndim < 1) {
|
|
44
|
+
rb_raise(cumo_na_eShapeError, "layer_norm needs an axis to normalize along");
|
|
45
|
+
}
|
|
46
|
+
y = cumo_na_new(cT, nx->ndim, nx->shape);
|
|
47
|
+
|
|
48
|
+
// Taking a pointer runs allocate, which is Ruby, so all four are taken
|
|
49
|
+
// before anything is measured and the checks below run over what is left.
|
|
50
|
+
// See row_method.h for why the pointers themselves have to be looked at
|
|
51
|
+
// again.
|
|
52
|
+
x_ptr = cumo_na_get_offset_pointer_for_read(x_cont);
|
|
53
|
+
gamma_ptr = cumo_na_get_offset_pointer_for_read(gamma_cont);
|
|
54
|
+
beta_ptr = cumo_na_get_offset_pointer_for_read(beta_cont);
|
|
55
|
+
y_ptr = cumo_na_get_offset_pointer_for_write(y);
|
|
56
|
+
|
|
57
|
+
// cumo_na_as_contiguous_array answers what dup gave it, which a class is
|
|
58
|
+
// free to define, so what these carry is not what was checked above.
|
|
59
|
+
CUMO_CHECK_NARRAY_TYPE(x_cont, cT);
|
|
60
|
+
CUMO_CHECK_NARRAY_TYPE(gamma_cont, cT);
|
|
61
|
+
CUMO_CHECK_NARRAY_TYPE(beta_cont, cT);
|
|
62
|
+
CumoGetNArray(x_cont, nx);
|
|
63
|
+
CumoGetNArray(gamma_cont, ngamma);
|
|
64
|
+
CumoGetNArray(beta_cont, nbeta);
|
|
65
|
+
CumoGetNArray(y, ny);
|
|
66
|
+
|
|
67
|
+
if (nx->ndim < 1) {
|
|
68
|
+
rb_raise(cumo_na_eShapeError, "layer_norm needs an axis to normalize along");
|
|
69
|
+
}
|
|
70
|
+
cols = nx->shape[nx->ndim - 1];
|
|
71
|
+
if (ngamma->ndim != 1 || ngamma->shape[0] != cols) {
|
|
72
|
+
rb_raise(cumo_na_eShapeError, "gamma must be 1-dimensional and %"SZF"u long", cols);
|
|
73
|
+
}
|
|
74
|
+
if (nbeta->ndim != 1 || nbeta->shape[0] != cols) {
|
|
75
|
+
rb_raise(cumo_na_eShapeError, "beta must be 1-dimensional and %"SZF"u long", cols);
|
|
76
|
+
}
|
|
77
|
+
CUMO_ROW_CHECK_SAME_SHAPE(ny, nx);
|
|
78
|
+
CUMO_ROW_CHECK_READ_BUFFER(x_cont, x_ptr, "self");
|
|
79
|
+
CUMO_ROW_CHECK_READ_BUFFER(gamma_cont, gamma_ptr, "gamma");
|
|
80
|
+
CUMO_ROW_CHECK_READ_BUFFER(beta_cont, beta_ptr, "beta");
|
|
81
|
+
CUMO_ROW_CHECK_WRITE_BUFFER(y, y_ptr, "the result");
|
|
82
|
+
|
|
83
|
+
if (nx->size == 0) {
|
|
84
|
+
return y;
|
|
85
|
+
}
|
|
86
|
+
rows = nx->size / cols;
|
|
87
|
+
|
|
88
|
+
<%="cumo_#{c_iter}_kernel_launch"%>(x_ptr, gamma_ptr, beta_ptr, y_ptr, (uint64_t)rows, (uint64_t)cols, double_eps);
|
|
89
|
+
|
|
90
|
+
return y;
|
|
91
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<%
|
|
2
|
+
acc = acc_type.empty? ? 'dtype' : acc_type
|
|
3
|
+
%>
|
|
4
|
+
|
|
5
|
+
#if defined(__cplusplus)
|
|
6
|
+
#if 0
|
|
7
|
+
{ /* satisfy cc-mode */
|
|
8
|
+
#endif
|
|
9
|
+
} /* extern "C" { */
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
struct <%="cumo_#{c_iter}_stats"%> {
|
|
13
|
+
<%=acc%> mean;
|
|
14
|
+
<%=acc%> rstd;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Layer norm divides the sum of squared deviations by n, where cumo's var
|
|
18
|
+
// divides by n - 1, so the two cannot share a MapOut however much of the
|
|
19
|
+
// accumulation they share.
|
|
20
|
+
struct <%="cumo_#{c_iter}_impl"%> : cumo_<%=type_name%>_moments_impl {
|
|
21
|
+
<%=acc%> eps;
|
|
22
|
+
__device__ <%="cumo_#{c_iter}_stats"%> MapOut(Moments accum) {
|
|
23
|
+
return {accum.mean, <%=acc%>(1) / sqrt(accum.m2 / accum.n + eps)};
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
struct <%="cumo_#{c_iter}_apply"%> {
|
|
28
|
+
const dtype* g;
|
|
29
|
+
const dtype* b;
|
|
30
|
+
__device__ dtype operator()(dtype x, uint64_t col, <%="cumo_#{c_iter}_stats"%> st) const {
|
|
31
|
+
<%=acc%> v = (<%=to_acc%>(x) - st.mean) * st.rstd;
|
|
32
|
+
return <%=from_acc%>(v * <%=to_acc%>(g[col]) + <%=to_acc%>(b[col]));
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
#if defined(__cplusplus)
|
|
37
|
+
extern "C" {
|
|
38
|
+
#if 0
|
|
39
|
+
} /* satisfy cc-mode */
|
|
40
|
+
#endif
|
|
41
|
+
#endif
|
|
42
|
+
|
|
43
|
+
void <%="cumo_#{c_iter}_kernel_launch"%>(
|
|
44
|
+
char *px, char *pg, char *pb, char *py,
|
|
45
|
+
uint64_t rows, uint64_t cols, double eps)
|
|
46
|
+
{
|
|
47
|
+
<%="cumo_#{c_iter}_impl"%> impl;
|
|
48
|
+
<%="cumo_#{c_iter}_apply"%> apply;
|
|
49
|
+
|
|
50
|
+
impl.eps = (<%=acc%>)eps;
|
|
51
|
+
apply.g = (const dtype*)pg;
|
|
52
|
+
apply.b = (const dtype*)pb;
|
|
53
|
+
cumo_row_reduce_apply<dtype>(px, py, rows, cols, impl, apply);
|
|
54
|
+
}
|
|
@@ -28,7 +28,7 @@ static void
|
|
|
28
28
|
<%=name%> of self.
|
|
29
29
|
<% if is_float %>
|
|
30
30
|
@overload <%=name%>(axis:nil, keepdims:false, nan:false)
|
|
31
|
-
@param [TrueClass] nan (keyword) If true,
|
|
31
|
+
@param [TrueClass] nan (keyword) If true, propagate NaN. If false, ignore NaN.
|
|
32
32
|
<% else %>
|
|
33
33
|
@overload <%=name%>(axis:nil, keepdims:false)
|
|
34
34
|
<% end %>
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
<% unless type_name == 'robject' %>
|
|
2
2
|
void <%="cumo_#{c_iter}_kernel_launch"%>(cumo_na_iarray_t* x, cumo_na_iarray_t* coef, int ncoef, cumo_na_iarray_t* z, cumo_na_indexer_t* indexer);
|
|
3
3
|
|
|
4
|
-
// Horner over every element in one launch. The host loop this replaces ran
|
|
5
|
-
// once per element, each time behind a cudaDeviceSynchronize.
|
|
6
4
|
static void
|
|
7
5
|
<%=c_iter%>_kernel(cumo_na_loop_t *const lp)
|
|
8
6
|
{
|
|
@@ -27,7 +25,6 @@ static void
|
|
|
27
25
|
dtype x, y, a;
|
|
28
26
|
|
|
29
27
|
CUMO_SHOW_SYNCHRONIZE_FIXME_WARNING_ONCE("<%=name%>", "<%=type_name%>");
|
|
30
|
-
cumo_cuda_runtime_device_synchronize();
|
|
31
28
|
n = lp->narg - 2;
|
|
32
29
|
x = *(dtype*)CUMO_NDL_PTR(lp,0);
|
|
33
30
|
y = *(dtype*)CUMO_NDL_PTR(lp,n);
|
|
@@ -40,7 +40,7 @@ void <%="cumo_#{c_iter}_kernel_launch"%>(cumo_na_iarray_t* x, cumo_na_iarray_t*
|
|
|
40
40
|
<%="cumo_#{c_iter}_kernel_dim"%><<<grid_dim, block_dim>>>(*x, d_coef, ncoef, *z, *indexer);
|
|
41
41
|
break;
|
|
42
42
|
}
|
|
43
|
-
|
|
43
|
+
cumo_check_launch_holding(d_coef);
|
|
44
44
|
cumo_cuda_runtime_free((char*)d_coef);
|
|
45
45
|
}
|
|
46
46
|
<% end %>
|