cumo 0.5.7 → 0.5.8
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/CHANGELOG.md +49 -0
- data/bench/bench.rb +235 -0
- data/ext/cumo/cuda/cublas.c +4 -2
- data/ext/cumo/cuda/cudnn.c +4 -2
- data/ext/cumo/cuda/driver.c +12 -4
- data/ext/cumo/cuda/memory_pool.cpp +104 -30
- data/ext/cumo/cuda/runtime.c +29 -0
- data/ext/cumo/include/cumo/cuda/cudnn.h +1 -1
- data/ext/cumo/include/cumo/cuda/cumo_thrust.hpp +8 -3
- data/ext/cumo/include/cumo/cuda/memory_pool.h +5 -0
- data/ext/cumo/include/cumo/cuda/runtime.h +12 -1
- data/ext/cumo/include/cumo/intern.h +5 -0
- data/ext/cumo/include/cumo/ndloop.h +1 -1
- data/ext/cumo/include/cumo/reduce_kernel.h +2 -0
- data/ext/cumo/include/cumo/template.h +18 -0
- data/ext/cumo/include/cumo/template_kernel.h +8 -1
- data/ext/cumo/include/cumo/types/complex_kernel.h +2 -2
- data/ext/cumo/include/cumo.h +2 -2
- data/ext/cumo/narray/array.c +1 -1
- data/ext/cumo/narray/data.c +32 -9
- data/ext/cumo/narray/data_kernel.cu +4 -0
- data/ext/cumo/narray/gen/spec.rb +1 -1
- data/ext/cumo/narray/gen/tmpl/accum.c +1 -0
- data/ext/cumo/narray/gen/tmpl/accum_arg.c +1 -0
- data/ext/cumo/narray/gen/tmpl/accum_binary_kernel.cu +13 -13
- data/ext/cumo/narray/gen/tmpl/accum_index.c +31 -4
- data/ext/cumo/narray/gen/tmpl/alloc_func.c +16 -6
- data/ext/cumo/narray/gen/tmpl/allocate.c +9 -0
- data/ext/cumo/narray/gen/tmpl/aref.c +6 -0
- data/ext/cumo/narray/gen/tmpl/aset.c +1 -1
- data/ext/cumo/narray/gen/tmpl/binary.c +11 -5
- data/ext/cumo/narray/gen/tmpl/binary2.c +24 -12
- data/ext/cumo/narray/gen/tmpl/binary2_kernel.cu +17 -3
- data/ext/cumo/narray/gen/tmpl/binary_kernel.cu +17 -4
- data/ext/cumo/narray/gen/tmpl/binary_s_kernel.cu +1 -0
- data/ext/cumo/narray/gen/tmpl/bincount.c +52 -10
- data/ext/cumo/narray/gen/tmpl/clip.c +70 -33
- data/ext/cumo/narray/gen/tmpl/clip_kernel.cu +58 -0
- data/ext/cumo/narray/gen/tmpl/complex_accum_kernel.cu +12 -4
- data/ext/cumo/narray/gen/tmpl/cond_binary_kernel.cu +1 -0
- data/ext/cumo/narray/gen/tmpl/ewcomp_kernel.cu +1 -0
- data/ext/cumo/narray/gen/tmpl/eye_kernel.cu +1 -0
- data/ext/cumo/narray/gen/tmpl/fill_kernel.cu +2 -0
- data/ext/cumo/narray/gen/tmpl/float_accum_kernel.cu +8 -0
- data/ext/cumo/narray/gen/tmpl/format.c +1 -1
- data/ext/cumo/narray/gen/tmpl/format_to_a.c +1 -1
- data/ext/cumo/narray/gen/tmpl/frexp.c +1 -0
- data/ext/cumo/narray/gen/tmpl/gemm.c +8 -2
- data/ext/cumo/narray/gen/tmpl/logseq.c +2 -1
- data/ext/cumo/narray/gen/tmpl/logseq_kernel.cu +2 -0
- data/ext/cumo/narray/gen/tmpl/median.c +1 -0
- data/ext/cumo/narray/gen/tmpl/minmax.c +9 -2
- data/ext/cumo/narray/gen/tmpl/new_dim0.c +8 -0
- data/ext/cumo/narray/gen/tmpl/new_dim0_kernel.cu +3 -0
- data/ext/cumo/narray/gen/tmpl/poly.c +9 -8
- data/ext/cumo/narray/gen/tmpl/pow_kernel.cu +2 -0
- data/ext/cumo/narray/gen/tmpl/rand.c +43 -14
- data/ext/cumo/narray/gen/tmpl/rand_kernel.cu +97 -0
- data/ext/cumo/narray/gen/tmpl/rand_norm.c +28 -57
- data/ext/cumo/narray/gen/tmpl/rand_norm_kernel.cu +73 -0
- data/ext/cumo/narray/gen/tmpl/real_accum_kernel.cu +43 -13
- data/ext/cumo/narray/gen/tmpl/seq.c +1 -1
- data/ext/cumo/narray/gen/tmpl/seq_kernel.cu +2 -0
- data/ext/cumo/narray/gen/tmpl/set2.c +1 -0
- data/ext/cumo/narray/gen/tmpl/store_array.c +20 -29
- data/ext/cumo/narray/gen/tmpl/store_array_kernel.cu +4 -0
- data/ext/cumo/narray/gen/tmpl/store_bit_kernel.cu +4 -0
- data/ext/cumo/narray/gen/tmpl/store_from_kernel.cu +4 -0
- data/ext/cumo/narray/gen/tmpl/unary.c +49 -34
- data/ext/cumo/narray/gen/tmpl/unary_kernel.cu +37 -15
- data/ext/cumo/narray/gen/tmpl/unary_ret2.c +1 -0
- data/ext/cumo/narray/gen/tmpl/unary_s_kernel.cu +4 -0
- data/ext/cumo/narray/gen/tmpl_bit/allocate.c +1 -0
- data/ext/cumo/narray/gen/tmpl_bit/aref.c +6 -0
- data/ext/cumo/narray/gen/tmpl_bit/bit_count_kernel.cu +4 -0
- data/ext/cumo/narray/gen/tmpl_bit/format.c +1 -1
- data/ext/cumo/narray/gen/tmpl_bit/format_to_a.c +1 -1
- data/ext/cumo/narray/gen/tmpl_bit/store_array.c +9 -10
- data/ext/cumo/narray/index.c +3 -3
- data/ext/cumo/narray/index_kernel.cu +11 -0
- data/ext/cumo/narray/narray.c +84 -21
- data/ext/cumo/narray/ndloop.c +64 -47
- data/ext/cumo/narray/ndloop_kernel.cu +2 -0
- data/ext/cumo/narray/rand.c +34 -2
- data/lib/cumo/narray/extra.rb +13 -12
- data/test/bit_test.rb +16 -0
- data/test/cuda/driver_test.rb +16 -0
- data/test/cuda/memory_pool_test.rb +123 -0
- data/test/cumo_test.rb +35 -0
- data/test/extra_test.rb +1095 -0
- data/test/math_test.rb +638 -0
- data/test/narray_alt_coverage_test.rb +856 -0
- data/test/narray_test.rb +831 -0
- metadata +11 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 12027799f8203c1e08409de1fdec1b550e26511547bbaee2a938488ecd19e079
|
|
4
|
+
data.tar.gz: 399daea2a183febf70ceeb1e6492479fd3a34ae29e994265c4da9f89644c3c4d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d12b0fa56578c8ffd309aa314ab40c98d3b983375d7ddd33b0909ac47092d52b48ea1b53173ea843701587a0fb50442a907b459bd558a8c92e4525eedd1531fd
|
|
7
|
+
data.tar.gz: ba1b2bac6afb811d790c3f8d90906f3e7f571cbbdb8dc59e3ff6141fa54aa5b8f8a8d6e597ff3045e001c9ae5ff0cab6d9e6679ee6b8f5bbdb1642f203ba19b1
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,52 @@
|
|
|
1
|
+
# 0.5.8 (2026/08/17)
|
|
2
|
+
|
|
3
|
+
Breaking changes:
|
|
4
|
+
|
|
5
|
+
* Random numbers are generated on the GPU, so `srand` no longer reproduces `Numo::NArray`'s values for the same seed. Reproducibility within Cumo is unchanged and no longer depends on how the calls are split (PR #223)
|
|
6
|
+
* Integer division by zero raises `ZeroDivisionError` instead of returning whatever the hardware produced, matching `Numo::NArray` (PR #218)
|
|
7
|
+
* A numeric index into an unallocated NArray raises instead of returning a view nothing ever reads (PR #225)
|
|
8
|
+
|
|
9
|
+
Fixes:
|
|
10
|
+
|
|
11
|
+
* Fix `rb_raise` from inside a C++ catch leaking the exception object (PR #229)
|
|
12
|
+
* Fix a failed Array store leaking its staging buffer, as much as the destination holds per failure (PR #228)
|
|
13
|
+
* Fix the GC free hook raising when a device free fails, which surfaced at whatever line triggered the collection (PR #227)
|
|
14
|
+
* Fix `from_binary` and `store_binary` writing managed memory while a kernel was still reading it (PR #226)
|
|
15
|
+
* Fix integer `sum` and `prod` truncating every partial the reduction merged (PR #224)
|
|
16
|
+
* Fix `minmax` ignoring compatible mode and returning zero-dimensional NArrays (PR #220)
|
|
17
|
+
* Fix `nan` poisoning `max` and an all-`nan` `min` answering `DBL_MAX` (PR #219)
|
|
18
|
+
* Fix `reshape!` and `marshal_load` mutating the array before they validate their arguments (PR #217)
|
|
19
|
+
* Fix `ptp` answering 1 whatever the input (PR #216)
|
|
20
|
+
* Fix `mulsum` ignoring the accumulator and the operand strides, which faulted the GPU on 8-bit dtypes (PR #215)
|
|
21
|
+
* Fix `dot` handing `gemm` operands of a different dtype, an out-of-bounds read (PR #214)
|
|
22
|
+
* Fix view offsets being added in the wrong unit (PR #213)
|
|
23
|
+
* Fix interpreter abort on a zero-dimensional view (PR #212)
|
|
24
|
+
* Fix `Cumo::RObject#logseq` returning `Infinity` (PR #210)
|
|
25
|
+
* Fix `to_i`, `to_f` and `to_c` recursing forever on a one-element array (PR #208)
|
|
26
|
+
* Fix complex `log2` and `log10` discarding the logarithm on the GPU (PR #207)
|
|
27
|
+
* Fix segfault in `max_index` and `min_index` with `nan: true` (PR #206)
|
|
28
|
+
* Fix `poly` failing on every input from an uninitialized `ndfunc` dimension (PR #205)
|
|
29
|
+
* Fix out-of-bounds writes in `bincount` from an overflowing length and a stale scan (PR #204)
|
|
30
|
+
* Fix `bincount` raising `TypeError` for every input (PR #203)
|
|
31
|
+
* Fix host loops reading device memory without synchronizing, which silently corrupted `minmax`, the `nan: true` reductions, `kahan_sum`, `modf`, `frexp` and `set_imag` (PR #202)
|
|
32
|
+
* Fix out-of-bounds access from three unchecked `size_t` multiplications (PR #201)
|
|
33
|
+
* Fix segfaults from an unvalidated marshal payload (PR #200)
|
|
34
|
+
* Fix `store_array` reading a source Array that its own elements rewrite (PR #198)
|
|
35
|
+
* Fix CUDA initialisation statuses being ignored at `require` time (PR #196)
|
|
36
|
+
* Fix kernel launch errors being discarded, which let a rejected launch return an untouched buffer as success (PR #195)
|
|
37
|
+
* Fix wrong `Cumo::RObject` results from driving host memory with asynchronous CUDA work (PR #194)
|
|
38
|
+
* Fix device memory being invisible to the GC, which let a program churning temporaries run the GPU out of memory (PR #221)
|
|
39
|
+
|
|
40
|
+
Changes:
|
|
41
|
+
|
|
42
|
+
* Run `rand` and `rand_norm` on the GPU (PR #223)
|
|
43
|
+
* Run `clip` on the GPU (PR #222)
|
|
44
|
+
* Correct spelling in messages, docs and comments (PR #211)
|
|
45
|
+
* Add the math, extra and narray test suites `numo-narray-alt` has and cumo lacked (PR #207, PR #208, PR #209)
|
|
46
|
+
* Add a `store_array` regression test for a shrunk source Array (PR #199)
|
|
47
|
+
* Stop the ccache cache growing past the 10 GB limit in CI (PR #197)
|
|
48
|
+
* Add a benchmark script under `bench/` (commit 3127f84)
|
|
49
|
+
|
|
1
50
|
# 0.5.7 (2026/08/13)
|
|
2
51
|
|
|
3
52
|
Breaking changes:
|
data/bench/bench.rb
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# ---------------------------------------------------------------------------
|
|
5
|
+
# Numo / Cumo comparison benchmark
|
|
6
|
+
#
|
|
7
|
+
# ruby bench.rb # CPU: numo-narray
|
|
8
|
+
# GPU=1 ruby bench.rb # GPU: cumo
|
|
9
|
+
# SIZE=4096 STEPS=500 ITER=3 ruby bench.rb
|
|
10
|
+
# PGM=0 ruby bench.rb # skip the PGM output
|
|
11
|
+
#
|
|
12
|
+
# To compare against numo-narray-alt (yoshoku's fork, SIMD enabled), swap the
|
|
13
|
+
# gem; this file needs no change, since the namespace stays Numo:
|
|
14
|
+
# gem uninstall numo-narray && gem install numo-narray-alt
|
|
15
|
+
#
|
|
16
|
+
# Environment variables that matter on the Cumo side:
|
|
17
|
+
# CUMO_SHOW_WARNING=ON warn wherever a CPU/GPU synchronization happens
|
|
18
|
+
# CUDA_LAUNCH_BLOCKING=1 synchronize on every launch (for profiling; slow)
|
|
19
|
+
# CUMO_MEMORY_POOL=OFF turn the memory pool off to measure allocation too
|
|
20
|
+
# CUDA_VISIBLE_DEVICES=0 device selection
|
|
21
|
+
#
|
|
22
|
+
# Numo::Bit, masked assignment and fancy indexing are deliberately unused. The
|
|
23
|
+
# divergence test is written with clip and floor arithmetic alone, so Numo and
|
|
24
|
+
# Cumo take exactly the same code path.
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
require 'benchmark'
|
|
28
|
+
|
|
29
|
+
GPU = !%w[0 false].include?(ENV['GPU'].to_s.downcase) && !ENV['GPU'].to_s.empty?
|
|
30
|
+
|
|
31
|
+
if GPU
|
|
32
|
+
require 'cumo/narray'
|
|
33
|
+
XM = Cumo
|
|
34
|
+
else
|
|
35
|
+
require 'numo/narray'
|
|
36
|
+
XM = Numo
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
SIZE = (ENV['SIZE'] || 1024).to_i # side of the mandelbrot / diffusion grid
|
|
40
|
+
STEPS = (ENV['STEPS'] || 200).to_i # number of iterations
|
|
41
|
+
NBODY = (ENV['NBODY'] || 2048).to_i # number of n-body particles
|
|
42
|
+
MC = (ENV['MC'] || 10_000_000).to_i
|
|
43
|
+
ITER = (ENV['ITER'] || 3).to_i # repeats per measurement (3-5 to steady it)
|
|
44
|
+
PGM = ENV['PGM'] != '0'
|
|
45
|
+
|
|
46
|
+
# --- helpers ---------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
# Cumo launches kernels asynchronously, so every measurement boundary syncs.
|
|
49
|
+
def sync
|
|
50
|
+
XM::CUDA::Runtime.cudaDeviceSynchronize if GPU
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Cumo returns a zero-dimensional NArray from sum / min / max / count_true,
|
|
54
|
+
# where Numo returns a Numeric.
|
|
55
|
+
def scalar(v)
|
|
56
|
+
return v if v.is_a?(Numeric)
|
|
57
|
+
v.respond_to?(:extract_cpu) ? v.extract_cpu : v.extract
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def free(*arrays)
|
|
61
|
+
return unless GPU
|
|
62
|
+
|
|
63
|
+
arrays.each { |a| a.free if a.respond_to?(:free) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def report(label)
|
|
67
|
+
result = nil
|
|
68
|
+
total = Benchmark.realtime do
|
|
69
|
+
ITER.times do
|
|
70
|
+
result = yield
|
|
71
|
+
sync
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
puts format(' %-12s %9.3f s total %9.3f s/iter', label, total, total / ITER)
|
|
75
|
+
result
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def write_pgm(path, arr)
|
|
79
|
+
return unless PGM
|
|
80
|
+
|
|
81
|
+
lo = scalar(arr.min).to_f
|
|
82
|
+
hi = scalar(arr.max).to_f
|
|
83
|
+
hi = lo + 1.0 if hi <= lo
|
|
84
|
+
img = XM::UInt8.cast((arr - lo) / (hi - lo) * 255.0)
|
|
85
|
+
h, w = img.shape
|
|
86
|
+
bytes =
|
|
87
|
+
begin
|
|
88
|
+
img.to_binary
|
|
89
|
+
rescue StandardError
|
|
90
|
+
# fallback for a Cumo without to_binary (device to host copy)
|
|
91
|
+
img.to_a.flatten.pack('C*')
|
|
92
|
+
end
|
|
93
|
+
File.open(path, 'wb') do |f|
|
|
94
|
+
f.write("P5\n#{w} #{h}\n255\n")
|
|
95
|
+
f.write(bytes)
|
|
96
|
+
end
|
|
97
|
+
free(img)
|
|
98
|
+
puts " -> #{path}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# --- 1. mandelbrot set (pure elementwise arithmetic) ------------------------
|
|
102
|
+
#
|
|
103
|
+
# Build an indicator that is 1.0 once diverged and 0.0 while still alive using
|
|
104
|
+
# clip and floor alone, then accumulate the survival count. No branches and no
|
|
105
|
+
# masks, so it maps straight onto the GPU.
|
|
106
|
+
def mandelbrot(n, steps)
|
|
107
|
+
lim = 1.0e10 # bound zr and zi here so neither Inf nor NaN appears
|
|
108
|
+
cr = XM::SFloat.new(1, n).seq / n * 3.0 - 2.0 # real axis [-2.0, 1.0)
|
|
109
|
+
ci = XM::SFloat.new(n, 1).seq / n * 3.0 - 1.5 # imaginary axis [-1.5, 1.5)
|
|
110
|
+
zr = XM::SFloat.zeros(n, n)
|
|
111
|
+
zi = XM::SFloat.zeros(n, n)
|
|
112
|
+
cnt = XM::SFloat.zeros(n, n)
|
|
113
|
+
cr = zr + cr # materialize the broadcast
|
|
114
|
+
ci = zr + ci
|
|
115
|
+
|
|
116
|
+
steps.times do
|
|
117
|
+
zr2 = zr * zr
|
|
118
|
+
zi2 = zi * zi
|
|
119
|
+
escaped = ((zr2 + zi2).clip(0.0, 4.0) / 4.0).floor # 1.0 once |z|^2 >= 4
|
|
120
|
+
cnt += 1.0 - escaped
|
|
121
|
+
zi = (2.0 * zr * zi + ci).clip(-lim, lim)
|
|
122
|
+
zr = (zr2 - zi2 + cr).clip(-lim, lim)
|
|
123
|
+
end
|
|
124
|
+
free(cr, ci, zr, zi)
|
|
125
|
+
cnt
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# --- 2. 2-D heat diffusion (stencil / slice arithmetic) ---------------------
|
|
129
|
+
def diffusion(n, steps)
|
|
130
|
+
u = XM::SFloat.zeros(n, n)
|
|
131
|
+
h = [n / 8, 1].max
|
|
132
|
+
u[h...(2 * h), h...(2 * h)] = 255.0
|
|
133
|
+
u[(5 * h)...(7 * h), (3 * h)...(4 * h)] = 255.0
|
|
134
|
+
k = 0.2
|
|
135
|
+
|
|
136
|
+
steps.times do
|
|
137
|
+
lap = u[0..-3, 1..-2] + u[2..-1, 1..-2] +
|
|
138
|
+
u[1..-2, 0..-3] + u[1..-2, 2..-1] - 4.0 * u[1..-2, 1..-2]
|
|
139
|
+
u[1..-2, 1..-2] = u[1..-2, 1..-2] + k * lap
|
|
140
|
+
end
|
|
141
|
+
u
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# --- 3. n-body (2-D, O(N^2) through broadcasting) ---------------------------
|
|
145
|
+
def nbody(n, steps)
|
|
146
|
+
dt = 1.0e-3
|
|
147
|
+
eps = 1.0e-3
|
|
148
|
+
x = XM::SFloat.new(n).rand * 2.0 - 1.0
|
|
149
|
+
y = XM::SFloat.new(n).rand * 2.0 - 1.0
|
|
150
|
+
vx = XM::SFloat.zeros(n)
|
|
151
|
+
vy = XM::SFloat.zeros(n)
|
|
152
|
+
|
|
153
|
+
steps.times do
|
|
154
|
+
dx = x.reshape(1, n) - x.reshape(n, 1) # [n, n]
|
|
155
|
+
dy = y.reshape(1, n) - y.reshape(n, 1)
|
|
156
|
+
r2 = dx * dx + dy * dy + eps
|
|
157
|
+
inv3 = 1.0 / (r2 * XM::NMath.sqrt(r2))
|
|
158
|
+
vx += (dx * inv3).sum(axis: 1) * dt
|
|
159
|
+
vy += (dy * inv3).sum(axis: 1) * dt
|
|
160
|
+
free(dx, dy, r2, inv3)
|
|
161
|
+
x += vx * dt
|
|
162
|
+
y += vy * dt
|
|
163
|
+
end
|
|
164
|
+
free(y, vx, vy)
|
|
165
|
+
x
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# --- 4. monte carlo pi (random numbers + reduction) -------------------------
|
|
169
|
+
# The sum is SFloat, so a large n costs pi its accuracy to rounding. Speed is
|
|
170
|
+
# what this measures.
|
|
171
|
+
def monte_carlo_pi(n)
|
|
172
|
+
x = XM::SFloat.new(n).rand
|
|
173
|
+
y = XM::SFloat.new(n).rand
|
|
174
|
+
r2 = x * x + y * y
|
|
175
|
+
inside = (1.0 - r2.clip(0.0, 1.0)).ceil # 1.0 where r2 < 1
|
|
176
|
+
hits = scalar(inside.sum).to_f
|
|
177
|
+
free(x, y, r2, inside)
|
|
178
|
+
4.0 * hits / n
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# --- 5. compatibility probe (eyeball the incompatibilities the README lists) -
|
|
182
|
+
def compat_probe
|
|
183
|
+
a = XM::SFloat[1.0, 2.0, 3.0]
|
|
184
|
+
bit = (a > 1.5)
|
|
185
|
+
count = bit.respond_to?(:count_true_cpu) ? bit.count_true_cpu : bit.count_true
|
|
186
|
+
puts " (a > 1.5).class : #{bit.class}"
|
|
187
|
+
puts " count_true : #{count.inspect}"
|
|
188
|
+
puts " a.sum.class : #{a.sum.class}" # Numo: Float / Cumo: zero-dimensional NArray
|
|
189
|
+
puts " a.max.class : #{a.max.class}"
|
|
190
|
+
puts " a[0].class : #{a[0].class}"
|
|
191
|
+
free(a)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# --- main ------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
version = begin
|
|
197
|
+
XM::NArray::VERSION
|
|
198
|
+
rescue StandardError
|
|
199
|
+
'unknown'
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
puts "ruby : #{RUBY_VERSION} (#{RUBY_PLATFORM})"
|
|
203
|
+
puts "backend : #{XM} #{version}"
|
|
204
|
+
puts "params : SIZE=#{SIZE} STEPS=#{STEPS} NBODY=#{NBODY} MC=#{MC} ITER=#{ITER}"
|
|
205
|
+
puts
|
|
206
|
+
|
|
207
|
+
# warm up (keep GPU initialization and kernel JIT out of the measurement)
|
|
208
|
+
warm = XM::SFloat.new(64, 64).seq
|
|
209
|
+
scalar((warm * warm + warm).sum)
|
|
210
|
+
sync
|
|
211
|
+
free(warm)
|
|
212
|
+
GC.start
|
|
213
|
+
|
|
214
|
+
puts 'compat:'
|
|
215
|
+
compat_probe
|
|
216
|
+
puts
|
|
217
|
+
|
|
218
|
+
puts 'bench:'
|
|
219
|
+
cnt = report('mandelbrot') { mandelbrot(SIZE, STEPS) }
|
|
220
|
+
write_pgm('mandelbrot.pgm', cnt)
|
|
221
|
+
free(cnt)
|
|
222
|
+
GC.start
|
|
223
|
+
|
|
224
|
+
u = report('diffusion') { diffusion(SIZE, STEPS) }
|
|
225
|
+
write_pgm('diffusion.pgm', u)
|
|
226
|
+
free(u)
|
|
227
|
+
GC.start
|
|
228
|
+
|
|
229
|
+
pos = report('nbody') { nbody(NBODY, 10) }
|
|
230
|
+
free(pos)
|
|
231
|
+
GC.start
|
|
232
|
+
|
|
233
|
+
pi = nil
|
|
234
|
+
report('monte_carlo') { pi = monte_carlo_pi(MC) }
|
|
235
|
+
puts format(' pi ~= %.6f', pi)
|
data/ext/cumo/cuda/cublas.c
CHANGED
|
@@ -51,14 +51,16 @@ cumo_cuda_cublas_handle()
|
|
|
51
51
|
if (handles == 0) {
|
|
52
52
|
int i;
|
|
53
53
|
int device_count = cumo_cuda_runtime_get_device_count();
|
|
54
|
-
handles =
|
|
54
|
+
handles = ALLOC_N(cublasHandle_t, device_count);
|
|
55
55
|
for (i = 0; i < device_count; ++i) {
|
|
56
56
|
handles[i] = 0;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
device = cumo_cuda_runtime_get_device();
|
|
60
60
|
if (handles[device] == 0) {
|
|
61
|
-
|
|
61
|
+
// A discarded status leaves the handle NULL, and cuBLAS reports that as
|
|
62
|
+
// CUBLAS_STATUS_NOT_INITIALIZED at the next call instead of the reason.
|
|
63
|
+
cumo_cuda_cublas_check_status(cublasCreate(&handles[device]));
|
|
62
64
|
}
|
|
63
65
|
return handles[device];
|
|
64
66
|
}
|
data/ext/cumo/cuda/cudnn.c
CHANGED
|
@@ -30,14 +30,16 @@ cumo_cuda_cudnn_handle()
|
|
|
30
30
|
if (handles == 0) {
|
|
31
31
|
int i;
|
|
32
32
|
int device_count = cumo_cuda_runtime_get_device_count();
|
|
33
|
-
handles =
|
|
33
|
+
handles = ALLOC_N(cudnnHandle_t, device_count);
|
|
34
34
|
for (i = 0; i < device_count; ++i) {
|
|
35
35
|
handles[i] = 0;
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
device = cumo_cuda_runtime_get_device();
|
|
39
39
|
if (handles[device] == 0) {
|
|
40
|
-
|
|
40
|
+
// A discarded status leaves the handle NULL, and cuDNN reports that as
|
|
41
|
+
// CUDNN_STATUS_NOT_INITIALIZED at the next call instead of the reason.
|
|
42
|
+
cumo_cuda_cudnn_check_status(cudnnCreate(&handles[device]));
|
|
41
43
|
}
|
|
42
44
|
return handles[device];
|
|
43
45
|
}
|
data/ext/cumo/cuda/driver.c
CHANGED
|
@@ -445,11 +445,19 @@ Init_cumo_cuda_driver()
|
|
|
445
445
|
rb_define_const(mDriver, "CU_JIT_INPUT_OBJECT", INT2NUM(CU_JIT_INPUT_OBJECT));
|
|
446
446
|
rb_define_const(mDriver, "CU_JIT_INPUT_PTX", INT2NUM(CU_JIT_INPUT_PTX));
|
|
447
447
|
|
|
448
|
-
cuInit(0);
|
|
449
|
-
|
|
448
|
+
check_status(cuInit(0));
|
|
449
|
+
|
|
450
|
+
// A driver API call needs a current context, and the runtime API only
|
|
451
|
+
// creates its primary one once an array operation happens, so this covers
|
|
452
|
+
// the gap in between. Losing it is not fatal -- everything but a driver
|
|
453
|
+
// call made before any array operation still works -- so a device that
|
|
454
|
+
// refuses a context is left for that call to report. cuDeviceGet leaves
|
|
455
|
+
// cuDevice untouched when it fails, hence the guard.
|
|
456
|
+
if (cuDeviceGet(&cuDevice, 0) == CUDA_SUCCESS) {
|
|
450
457
|
#if defined(CUDA_VERSION) && CUDA_VERSION >= 13000
|
|
451
|
-
|
|
458
|
+
cuCtxCreate(&context, NULL, 0, cuDevice);
|
|
452
459
|
#else
|
|
453
|
-
|
|
460
|
+
cuCtxCreate(&context, 0, cuDevice);
|
|
454
461
|
#endif
|
|
462
|
+
}
|
|
455
463
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
#include "cumo/cuda/memory_pool.h"
|
|
5
5
|
#include "cumo/cuda/runtime.h"
|
|
6
6
|
|
|
7
|
+
#include <cstdio>
|
|
7
8
|
#include <cstdlib>
|
|
8
9
|
#include <string>
|
|
9
10
|
|
|
@@ -19,36 +20,83 @@ static bool memory_pool_enabled;
|
|
|
19
20
|
|
|
20
21
|
VALUE cumo_cuda_eOutOfMemoryError;
|
|
21
22
|
|
|
23
|
+
// How a call into the pool ended, so that the raise can wait until the handler
|
|
24
|
+
// has been left. rb_raise longjmps, and a longjmp out of an active handler
|
|
25
|
+
// skips __cxa_end_catch: the exception object is never destroyed and leaks for
|
|
26
|
+
// the life of the process.
|
|
27
|
+
enum pool_outcome {
|
|
28
|
+
POOL_OK,
|
|
29
|
+
POOL_CUDA_ERROR,
|
|
30
|
+
POOL_OUT_OF_MEMORY,
|
|
31
|
+
POOL_UNKNOWN_ERROR,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
struct pool_error {
|
|
35
|
+
cudaError_t status;
|
|
36
|
+
char message[256];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
static enum pool_outcome
|
|
40
|
+
pool_malloc(size_t size, char **ptr, struct pool_error *err)
|
|
41
|
+
{
|
|
42
|
+
try {
|
|
43
|
+
// TODO(sonots): Get current CUDA stream and pass it
|
|
44
|
+
*ptr = reinterpret_cast<char*>(pool.Malloc(size));
|
|
45
|
+
return POOL_OK;
|
|
46
|
+
} catch (const cumo::internal::CUDARuntimeError& e) {
|
|
47
|
+
err->status = e.status();
|
|
48
|
+
return POOL_CUDA_ERROR;
|
|
49
|
+
} catch (const cumo::internal::OutOfMemoryError& e) {
|
|
50
|
+
// A std::string here would in turn be leaked by the raise.
|
|
51
|
+
std::snprintf(err->message, sizeof(err->message), "%s", e.what());
|
|
52
|
+
return POOL_OUT_OF_MEMORY;
|
|
53
|
+
} catch (const std::exception& e) {
|
|
54
|
+
// Nothing may reach the C caller: it has no handler, so an escaping
|
|
55
|
+
// exception is std::terminate.
|
|
56
|
+
std::snprintf(err->message, sizeof(err->message), "%s", e.what());
|
|
57
|
+
return POOL_UNKNOWN_ERROR;
|
|
58
|
+
} catch (...) {
|
|
59
|
+
std::snprintf(err->message, sizeof(err->message), "unknown C++ exception");
|
|
60
|
+
return POOL_UNKNOWN_ERROR;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
22
64
|
char*
|
|
23
65
|
cumo_cuda_runtime_malloc(size_t size)
|
|
24
66
|
{
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
67
|
+
char *ptr = 0;
|
|
68
|
+
struct pool_error err;
|
|
69
|
+
enum pool_outcome outcome;
|
|
70
|
+
|
|
71
|
+
if (!memory_pool_enabled) {
|
|
72
|
+
void *raw = 0;
|
|
73
|
+
cumo_cuda_runtime_check_status(cudaMallocManaged(&raw, size, cudaMemAttachGlobal));
|
|
74
|
+
return reinterpret_cast<char*>(raw);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
outcome = pool_malloc(size, &ptr, &err);
|
|
78
|
+
if (outcome == POOL_OUT_OF_MEMORY) {
|
|
79
|
+
// retry after GC
|
|
80
|
+
rb_funcall(rb_define_module("GC"), rb_intern("start"), 0);
|
|
81
|
+
outcome = pool_malloc(size, &ptr, &err);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
switch (outcome) {
|
|
85
|
+
case POOL_OK:
|
|
86
|
+
return ptr;
|
|
87
|
+
case POOL_CUDA_ERROR:
|
|
88
|
+
cumo_cuda_runtime_check_status(err.status);
|
|
89
|
+
break;
|
|
90
|
+
case POOL_OUT_OF_MEMORY:
|
|
91
|
+
rb_raise(cumo_cuda_eOutOfMemoryError, "%s", err.message);
|
|
92
|
+
case POOL_UNKNOWN_ERROR:
|
|
93
|
+
rb_raise(rb_eRuntimeError, "%s", err.message);
|
|
46
94
|
}
|
|
47
95
|
return 0; // should not reach here
|
|
48
96
|
}
|
|
49
97
|
|
|
50
|
-
|
|
51
|
-
|
|
98
|
+
static cudaError_t
|
|
99
|
+
runtime_free(char *ptr)
|
|
52
100
|
{
|
|
53
101
|
// Always offer the pointer to the pool first, whatever memory_pool_enabled
|
|
54
102
|
// says now: MemoryPool.enable/disable is public, so the state can differ
|
|
@@ -57,14 +105,35 @@ cumo_cuda_runtime_free(char *ptr)
|
|
|
57
105
|
// which is not at the head of its buffer.
|
|
58
106
|
try {
|
|
59
107
|
if (pool.Free(reinterpret_cast<intptr_t>(ptr))) {
|
|
60
|
-
return;
|
|
108
|
+
return cudaSuccess;
|
|
61
109
|
}
|
|
62
110
|
} catch (const cumo::internal::CUDARuntimeError& e) {
|
|
63
|
-
|
|
64
|
-
|
|
111
|
+
return e.status();
|
|
112
|
+
} catch (...) {
|
|
113
|
+
return cudaErrorUnknown;
|
|
65
114
|
}
|
|
66
115
|
// No pool owns it, so it came straight from cudaMallocManaged.
|
|
67
|
-
|
|
116
|
+
return cudaFree((void*)ptr);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
void
|
|
120
|
+
cumo_cuda_runtime_free(char *ptr)
|
|
121
|
+
{
|
|
122
|
+
cumo_cuda_runtime_check_status(runtime_free(ptr));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
void
|
|
126
|
+
cumo_cuda_runtime_free_no_raise(char *ptr)
|
|
127
|
+
{
|
|
128
|
+
cudaError_t status = runtime_free(ptr);
|
|
129
|
+
// rb_raise from a GC free hook longjmps out of the sweep, so report the
|
|
130
|
+
// failure the way ~Memory does. cudaFree only fails once the context is
|
|
131
|
+
// unusable, and the next runtime call reports the same status anyway.
|
|
132
|
+
if (status == cudaSuccess || status == cudaErrorCudartUnloading) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
std::fprintf(stderr, "cumo: failed to free device memory: %s (error=%d)\n",
|
|
136
|
+
cudaGetErrorString(status), static_cast<int>(status));
|
|
68
137
|
}
|
|
69
138
|
|
|
70
139
|
/*
|
|
@@ -110,17 +179,22 @@ rb_memory_pool_enabled_p(VALUE self)
|
|
|
110
179
|
static VALUE
|
|
111
180
|
rb_memory_pool_free_all_blocks(int argc, VALUE* argv, VALUE self)
|
|
112
181
|
{
|
|
182
|
+
// TODO(sonots): FIX if we create a Stream object
|
|
183
|
+
cudaStream_t stream_ptr = (argc < 1) ? 0 : (cudaStream_t)NUM2SIZET(argv[0]);
|
|
184
|
+
cudaError_t status = cudaSuccess;
|
|
185
|
+
|
|
113
186
|
try {
|
|
114
187
|
if (argc < 1) {
|
|
115
188
|
pool.FreeAllBlocks();
|
|
116
189
|
} else {
|
|
117
|
-
// TODO(sonots): FIX if we create a Stream object
|
|
118
|
-
cudaStream_t stream_ptr = (cudaStream_t)NUM2SIZET(argv[0]);
|
|
119
190
|
pool.FreeAllBlocks(stream_ptr);
|
|
120
191
|
}
|
|
121
192
|
} catch (const cumo::internal::CUDARuntimeError& e) {
|
|
122
|
-
|
|
193
|
+
status = e.status();
|
|
194
|
+
} catch (...) {
|
|
195
|
+
status = cudaErrorUnknown;
|
|
123
196
|
}
|
|
197
|
+
cumo_cuda_runtime_check_status(status);
|
|
124
198
|
return Qnil;
|
|
125
199
|
}
|
|
126
200
|
|
data/ext/cumo/cuda/runtime.c
CHANGED
|
@@ -10,6 +10,35 @@ VALUE cumo_cuda_mRuntime;
|
|
|
10
10
|
|
|
11
11
|
#define check_status(status) (cumo_cuda_runtime_check_status((status)))
|
|
12
12
|
|
|
13
|
+
// Called right after a <<<>>> launch. cudaGetLastError() reports errors the
|
|
14
|
+
// launch itself was rejected for; a fault while the kernel runs is asynchronous
|
|
15
|
+
// and still surfaces at a later call.
|
|
16
|
+
void
|
|
17
|
+
cumo_cuda_runtime_check_kernel_launch(void)
|
|
18
|
+
{
|
|
19
|
+
check_status(cudaGetLastError());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
int*
|
|
23
|
+
cumo_cuda_runtime_error_flag_new(void)
|
|
24
|
+
{
|
|
25
|
+
static int *flag = NULL;
|
|
26
|
+
|
|
27
|
+
if (flag == NULL) {
|
|
28
|
+
check_status(cudaHostAlloc((void**)&flag, sizeof(int),
|
|
29
|
+
cudaHostAllocMapped | cudaHostAllocPortable));
|
|
30
|
+
}
|
|
31
|
+
*flag = 0;
|
|
32
|
+
return flag;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
bool
|
|
36
|
+
cumo_cuda_runtime_error_flag_get(int *flag)
|
|
37
|
+
{
|
|
38
|
+
check_status(cudaDeviceSynchronize());
|
|
39
|
+
return (*flag != 0);
|
|
40
|
+
}
|
|
41
|
+
|
|
13
42
|
///////////////////////////////////////////
|
|
14
43
|
// Version Management
|
|
15
44
|
///////////////////////////////////////////
|
|
@@ -53,7 +53,12 @@ class cumo_thrust_strided_range
|
|
|
53
53
|
|
|
54
54
|
// construct strided_range for the range [first,last)
|
|
55
55
|
cumo_thrust_strided_range(Iterator first, Iterator last, difference_type stride)
|
|
56
|
-
: first(first),
|
|
56
|
+
: first(first), stride(stride), count(((last - first) + (stride - 1)) / stride) {}
|
|
57
|
+
|
|
58
|
+
// A stride of 0 repeats one element and a negative stride walks backwards, so
|
|
59
|
+
// neither can be counted from (last - first); those callers pass the count.
|
|
60
|
+
cumo_thrust_strided_range(Iterator first, difference_type stride, difference_type count)
|
|
61
|
+
: first(first), stride(stride), count(count) {}
|
|
57
62
|
|
|
58
63
|
iterator begin(void) const
|
|
59
64
|
{
|
|
@@ -62,13 +67,13 @@ class cumo_thrust_strided_range
|
|
|
62
67
|
|
|
63
68
|
iterator end(void) const
|
|
64
69
|
{
|
|
65
|
-
return begin() +
|
|
70
|
+
return begin() + count;
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
protected:
|
|
69
74
|
Iterator first;
|
|
70
|
-
Iterator last;
|
|
71
75
|
difference_type stride;
|
|
76
|
+
difference_type count;
|
|
72
77
|
};
|
|
73
78
|
|
|
74
79
|
|
|
@@ -18,6 +18,11 @@ cumo_cuda_runtime_malloc(size_t size);
|
|
|
18
18
|
void
|
|
19
19
|
cumo_cuda_runtime_free(char *ptr);
|
|
20
20
|
|
|
21
|
+
// For GC free hooks, which cannot take a raise: reports a failed free on stderr
|
|
22
|
+
// instead of raising.
|
|
23
|
+
void
|
|
24
|
+
cumo_cuda_runtime_free_no_raise(char *ptr);
|
|
25
|
+
|
|
21
26
|
#if defined(__cplusplus)
|
|
22
27
|
#if 0
|
|
23
28
|
{ /* satisfy cc-mode */
|
|
@@ -45,9 +45,20 @@ cumo_cuda_runtime_is_device_memory(void* ptr)
|
|
|
45
45
|
if (!ptr) { return false; }
|
|
46
46
|
status = cudaPointerGetAttributes(&attrs, ptr);
|
|
47
47
|
cudaGetLastError(); // reset last error to success
|
|
48
|
-
|
|
48
|
+
// Since CUDA 11 this succeeds for host memory as well and reports the kind
|
|
49
|
+
// in attrs.type, so the status alone no longer tells the two apart.
|
|
50
|
+
if (status != cudaSuccess) { return false; }
|
|
51
|
+
return (attrs.type != cudaMemoryTypeUnregistered);
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
// A kernel cannot raise, so it reports a bad argument through this flag and the
|
|
55
|
+
// caller turns it into an exception. The buffer is pinned host memory the device
|
|
56
|
+
// writes through, not pool memory: a four-byte pool allocation lands in the same
|
|
57
|
+
// bin as a small output array, and the managed page then migrates back and forth
|
|
58
|
+
// once per operation.
|
|
59
|
+
int* cumo_cuda_runtime_error_flag_new(void);
|
|
60
|
+
bool cumo_cuda_runtime_error_flag_get(int *flag);
|
|
61
|
+
|
|
51
62
|
#if defined(__cplusplus)
|
|
52
63
|
#if 0
|
|
53
64
|
{ /* satisfy cc-mode */
|
|
@@ -84,6 +84,11 @@ VALUE cumo_na_at_main(int nidx, VALUE *idx, VALUE self, int keep_dim, int result
|
|
|
84
84
|
// defined in array, used in math
|
|
85
85
|
VALUE cumo_na_ary_composition_dtype(VALUE ary);
|
|
86
86
|
|
|
87
|
+
// defined in rand, used by the generated rand and rand_norm
|
|
88
|
+
u_int64_t cumo_cuda_rand_seed(void);
|
|
89
|
+
u_int64_t cumo_cuda_rand_offset(void);
|
|
90
|
+
void cumo_cuda_rand_set_offset(u_int64_t offset);
|
|
91
|
+
|
|
87
92
|
#include "ruby/version.h"
|
|
88
93
|
|
|
89
94
|
#if RUBY_API_VERSION_CODE == 20100 // 2.1.0
|