cumo 0.5.6 → 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 +74 -0
- data/Rakefile +11 -2
- 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 +29 -11
- data/ext/cumo/cuda/memory_pool.cpp +104 -31
- data/ext/cumo/cuda/memory_pool_impl.cpp +37 -11
- data/ext/cumo/cuda/memory_pool_impl.hpp +59 -30
- data/ext/cumo/cuda/memory_pool_impl_test.cpp +143 -0
- data/ext/cumo/cuda/nvrtc.c +10 -4
- 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/handle.h +85 -0
- 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/types/robj_macro.h +0 -7
- data/ext/cumo/include/cumo.h +2 -2
- data/ext/cumo/narray/array.c +1 -1
- data/ext/cumo/narray/data.c +45 -10
- data/ext/cumo/narray/data_kernel.cu +4 -0
- data/ext/cumo/narray/gen/spec.rb +2 -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 +20 -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 +9 -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 +48 -18
- data/ext/cumo/narray/index_kernel.cu +11 -0
- data/ext/cumo/narray/narray.c +105 -29
- 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/cuda/nvrtc_program.rb +3 -1
- data/lib/cumo/narray/extra.rb +55 -13
- data/test/bit_test.rb +16 -0
- data/test/cuda/driver_test.rb +43 -0
- data/test/cuda/memory_pool_test.rb +123 -0
- data/test/cuda/nvrtc_test.rb +14 -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 +1070 -0
- metadata +13 -2
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,77 @@
|
|
|
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
|
+
|
|
50
|
+
# 0.5.7 (2026/08/13)
|
|
51
|
+
|
|
52
|
+
Breaking changes:
|
|
53
|
+
|
|
54
|
+
* `reshape` raises `ArgumentError` instead of `RangeError` for a dimension above `INT_MAX` (PR #183)
|
|
55
|
+
* `parse` accepts only numeric literals and `true`/`false`/`nil`, so `parse("3/4")` raises `ArgumentError` instead of returning `[[0]]` (PR #190)
|
|
56
|
+
|
|
57
|
+
Fixes:
|
|
58
|
+
|
|
59
|
+
* Fix segfaults from CUDA handles the process did not create, and from destroying one twice (PR #192)
|
|
60
|
+
* Fix process abort from freeing NArray data with the wrong allocator, including a raise out of GC for `Cumo::RObject` (PR #191)
|
|
61
|
+
* Fix arbitrary code execution in `parse`, which ran `eval` on every token (PR #190)
|
|
62
|
+
* Fix stack buffer overflow in `Cumo::RObject#format` with an element longer than 47 characters (PR #189)
|
|
63
|
+
* Fix a freed chunk returning to the arena of the wrong stream (PR #187)
|
|
64
|
+
* Fix `Malloc` searching the free list with the default stream's index (PR #186)
|
|
65
|
+
* Fix data race on the memory pool chunk graph and on the per-device pool table (PR #185)
|
|
66
|
+
* Fix a SIGFPE on fractional range steps and an out-of-bounds read on newaxis (PR #184)
|
|
67
|
+
* Fix a SIGFPE and a silently corrupt shape from unchecked `reshape` arguments (PR #183)
|
|
68
|
+
* Fix segfault when `store_binary` receives a non-String (PR #182)
|
|
69
|
+
* Fix missing range check on NArray indices, which segfaulted on a negative one (PR #181)
|
|
70
|
+
|
|
71
|
+
Changes:
|
|
72
|
+
|
|
73
|
+
* Compile the memory pool test harness in CI, and run it from `rake test` (PR #188)
|
|
74
|
+
|
|
1
75
|
# 0.5.6 (2026/08/09)
|
|
2
76
|
|
|
3
77
|
Fixes:
|
data/Rakefile
CHANGED
|
@@ -12,10 +12,19 @@ end
|
|
|
12
12
|
require "rake/extensiontask"
|
|
13
13
|
Rake::ExtensionTask.new("cumo")
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
desc 'Build the C++ memory pool test harness without running it (running it needs a GPU)'
|
|
16
|
+
task :build_ctest do
|
|
17
|
+
sh 'cd ext/cumo && ruby extconf.rb && make build-ctest'
|
|
17
18
|
end
|
|
18
19
|
|
|
20
|
+
task :ctest => :build_ctest do
|
|
21
|
+
sh 'cd ext/cumo && make run-ctest'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Both need a GPU, so CI runs neither. Tie them together so the one place
|
|
25
|
+
# they do run does not skip half of them.
|
|
26
|
+
task :test => :ctest
|
|
27
|
+
|
|
19
28
|
task :docs do
|
|
20
29
|
dir = "ext/cumo"
|
|
21
30
|
srcs = %w[array.c data.c index.c math.c narray.c rand.c struct.c].map { |s| File.join(dir, "narray", s) }
|
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
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
#include <cuda.h>
|
|
4
4
|
#include <cuda_runtime.h>
|
|
5
5
|
#include "cumo/cuda/driver.h"
|
|
6
|
+
#include "cumo/cuda/handle.h"
|
|
6
7
|
|
|
7
8
|
VALUE cumo_cuda_eDriverError;
|
|
8
9
|
VALUE cumo_cuda_mDriver;
|
|
9
10
|
#define eDriverError cumo_cuda_eDriverError
|
|
10
11
|
#define mDriver cumo_cuda_mDriver
|
|
11
12
|
|
|
13
|
+
static cumo_cuda_handle_set_t link_states;
|
|
14
|
+
static cumo_cuda_handle_set_t modules;
|
|
15
|
+
|
|
12
16
|
static void
|
|
13
17
|
check_status(CUresult status)
|
|
14
18
|
{
|
|
@@ -100,13 +104,13 @@ cuLinkAddData_without_gvl_cb(void *param)
|
|
|
100
104
|
static VALUE
|
|
101
105
|
rb_cuLinkAddData(VALUE self, VALUE state, VALUE type, VALUE data, VALUE name)
|
|
102
106
|
{
|
|
103
|
-
CUlinkState _state = (CUlinkState)NUM2SIZET(state);
|
|
104
107
|
CUjitInputType _type = (CUjitInputType)NUM2INT(type);
|
|
105
108
|
// The image may be a cubin, so it is taken by length and allowed to hold
|
|
106
109
|
// NUL bytes; the name is a plain C string cuLinkAddData reports errors with.
|
|
107
110
|
void* _data = (void *)StringValuePtr(data);
|
|
108
111
|
size_t _size = RSTRING_LEN(data);
|
|
109
112
|
const char* _name = StringValueCStr(name);
|
|
113
|
+
CUlinkState _state = (CUlinkState)cumo_cuda_handle_get(&link_states, state, "CUlinkState");
|
|
110
114
|
CUresult status;
|
|
111
115
|
|
|
112
116
|
struct cuLinkAddDataParam param = {_state, _type, _data, _size, _name, 0, (CUjit_option*)0, (void**)0};
|
|
@@ -141,9 +145,9 @@ cuLinkAddFile_without_gvl_cb(void *param)
|
|
|
141
145
|
static VALUE
|
|
142
146
|
rb_cuLinkAddFile(VALUE self, VALUE state, VALUE type, VALUE path)
|
|
143
147
|
{
|
|
144
|
-
CUlinkState _state = (CUlinkState)NUM2SIZET(state);
|
|
145
148
|
CUjitInputType _type = (CUjitInputType)NUM2INT(type);
|
|
146
149
|
const char* _path = StringValueCStr(path);
|
|
150
|
+
CUlinkState _state = (CUlinkState)cumo_cuda_handle_get(&link_states, state, "CUlinkState");
|
|
147
151
|
CUresult status;
|
|
148
152
|
|
|
149
153
|
struct cuLinkAddFileParam param = {_state, _type, _path, 0, (CUjit_option*)0, (void **)0};
|
|
@@ -173,7 +177,7 @@ cuLinkComplete_without_gvl_cb(void *param)
|
|
|
173
177
|
static VALUE
|
|
174
178
|
rb_cuLinkComplete(VALUE self, VALUE state)
|
|
175
179
|
{
|
|
176
|
-
CUlinkState _state = (CUlinkState)
|
|
180
|
+
CUlinkState _state = (CUlinkState)cumo_cuda_handle_get(&link_states, state, "CUlinkState");
|
|
177
181
|
void* _cubinOut;
|
|
178
182
|
size_t _sizeOut;
|
|
179
183
|
CUresult status;
|
|
@@ -214,6 +218,7 @@ rb_cuLinkCreate(VALUE self)
|
|
|
214
218
|
//status = cuLinkCreate(0, (CUjit_option*)0, (void**)0, &state);
|
|
215
219
|
|
|
216
220
|
check_status(status);
|
|
221
|
+
cumo_cuda_handle_set_add(&link_states, (size_t)state);
|
|
217
222
|
return SIZET2NUM((size_t)state);
|
|
218
223
|
}
|
|
219
224
|
|
|
@@ -233,7 +238,7 @@ cuLinkDestroy_without_gvl_cb(void *param)
|
|
|
233
238
|
static VALUE
|
|
234
239
|
rb_cuLinkDestroy(VALUE self, VALUE state)
|
|
235
240
|
{
|
|
236
|
-
CUlinkState _state = (CUlinkState)
|
|
241
|
+
CUlinkState _state = (CUlinkState)cumo_cuda_handle_take(&link_states, state, "CUlinkState");
|
|
237
242
|
CUresult status;
|
|
238
243
|
|
|
239
244
|
struct cuLinkDestroyParam param = {_state};
|
|
@@ -263,8 +268,8 @@ static VALUE
|
|
|
263
268
|
rb_cuModuleGetFunction(VALUE self, VALUE hmod, VALUE name)
|
|
264
269
|
{
|
|
265
270
|
CUfunction _hfunc;
|
|
266
|
-
CUmodule _hmod = (CUmodule)NUM2SIZET(hmod);
|
|
267
271
|
const char* _name = StringValueCStr(name);
|
|
272
|
+
CUmodule _hmod = (CUmodule)cumo_cuda_handle_get(&modules, hmod, "CUmodule");
|
|
268
273
|
CUresult status;
|
|
269
274
|
|
|
270
275
|
struct cuModuleGetFunctionParam param = {&_hfunc, _hmod, _name};
|
|
@@ -297,8 +302,8 @@ rb_cuModuleGetGlobal(VALUE self, VALUE hmod, VALUE name)
|
|
|
297
302
|
{
|
|
298
303
|
CUdeviceptr _dptr;
|
|
299
304
|
size_t _bytes;
|
|
300
|
-
CUmodule _hmod = (CUmodule)NUM2SIZET(hmod);
|
|
301
305
|
const char* _name = StringValueCStr(name);
|
|
306
|
+
CUmodule _hmod = (CUmodule)cumo_cuda_handle_get(&modules, hmod, "CUmodule");
|
|
302
307
|
CUresult status;
|
|
303
308
|
VALUE ret;
|
|
304
309
|
|
|
@@ -342,6 +347,7 @@ rb_cuModuleLoad(VALUE self, VALUE fname)
|
|
|
342
347
|
|
|
343
348
|
RB_GC_GUARD(fname);
|
|
344
349
|
check_status(status);
|
|
350
|
+
cumo_cuda_handle_set_add(&modules, (size_t)_module);
|
|
345
351
|
return SIZET2NUM((size_t)_module);
|
|
346
352
|
}
|
|
347
353
|
|
|
@@ -373,6 +379,7 @@ rb_cuModuleLoadData(VALUE self, VALUE image)
|
|
|
373
379
|
|
|
374
380
|
RB_GC_GUARD(image);
|
|
375
381
|
check_status(status);
|
|
382
|
+
cumo_cuda_handle_set_add(&modules, (size_t)_module);
|
|
376
383
|
return SIZET2NUM((size_t)_module);
|
|
377
384
|
}
|
|
378
385
|
|
|
@@ -392,7 +399,7 @@ cuModuleUnload_without_gvl_cb(void *param)
|
|
|
392
399
|
static VALUE
|
|
393
400
|
rb_cuModuleUnload(VALUE self, VALUE hmod)
|
|
394
401
|
{
|
|
395
|
-
CUmodule _hmod = (CUmodule)
|
|
402
|
+
CUmodule _hmod = (CUmodule)cumo_cuda_handle_take(&modules, hmod, "CUmodule");
|
|
396
403
|
CUresult status;
|
|
397
404
|
|
|
398
405
|
struct cuModuleUnloadParam param = {_hmod};
|
|
@@ -414,6 +421,9 @@ Init_cumo_cuda_driver()
|
|
|
414
421
|
mDriver = rb_define_module_under(mCUDA, "Driver");
|
|
415
422
|
eDriverError = rb_define_class_under(mCUDA, "DriverError", rb_eStandardError);
|
|
416
423
|
|
|
424
|
+
cumo_cuda_handle_set_init(&link_states);
|
|
425
|
+
cumo_cuda_handle_set_init(&modules);
|
|
426
|
+
|
|
417
427
|
rb_define_singleton_method(mDriver, "cuCtxGetCurrent", rb_cuCtxGetCurrent, 0);
|
|
418
428
|
rb_define_singleton_method(mDriver, "cuLinkAddData", rb_cuLinkAddData, 4);
|
|
419
429
|
rb_define_singleton_method(mDriver, "cuLinkAddFile", rb_cuLinkAddFile, 3);
|
|
@@ -435,11 +445,19 @@ Init_cumo_cuda_driver()
|
|
|
435
445
|
rb_define_const(mDriver, "CU_JIT_INPUT_OBJECT", INT2NUM(CU_JIT_INPUT_OBJECT));
|
|
436
446
|
rb_define_const(mDriver, "CU_JIT_INPUT_PTX", INT2NUM(CU_JIT_INPUT_PTX));
|
|
437
447
|
|
|
438
|
-
cuInit(0);
|
|
439
|
-
|
|
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) {
|
|
440
457
|
#if defined(CUDA_VERSION) && CUDA_VERSION >= 13000
|
|
441
|
-
|
|
458
|
+
cuCtxCreate(&context, NULL, 0, cuDevice);
|
|
442
459
|
#else
|
|
443
|
-
|
|
460
|
+
cuCtxCreate(&context, 0, cuDevice);
|
|
444
461
|
#endif
|
|
462
|
+
}
|
|
445
463
|
}
|