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.
Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +74 -0
  3. data/Rakefile +11 -2
  4. data/bench/bench.rb +235 -0
  5. data/ext/cumo/cuda/cublas.c +4 -2
  6. data/ext/cumo/cuda/cudnn.c +4 -2
  7. data/ext/cumo/cuda/driver.c +29 -11
  8. data/ext/cumo/cuda/memory_pool.cpp +104 -31
  9. data/ext/cumo/cuda/memory_pool_impl.cpp +37 -11
  10. data/ext/cumo/cuda/memory_pool_impl.hpp +59 -30
  11. data/ext/cumo/cuda/memory_pool_impl_test.cpp +143 -0
  12. data/ext/cumo/cuda/nvrtc.c +10 -4
  13. data/ext/cumo/cuda/runtime.c +29 -0
  14. data/ext/cumo/include/cumo/cuda/cudnn.h +1 -1
  15. data/ext/cumo/include/cumo/cuda/cumo_thrust.hpp +8 -3
  16. data/ext/cumo/include/cumo/cuda/handle.h +85 -0
  17. data/ext/cumo/include/cumo/cuda/memory_pool.h +5 -0
  18. data/ext/cumo/include/cumo/cuda/runtime.h +12 -1
  19. data/ext/cumo/include/cumo/intern.h +5 -0
  20. data/ext/cumo/include/cumo/ndloop.h +1 -1
  21. data/ext/cumo/include/cumo/reduce_kernel.h +2 -0
  22. data/ext/cumo/include/cumo/template.h +18 -0
  23. data/ext/cumo/include/cumo/template_kernel.h +8 -1
  24. data/ext/cumo/include/cumo/types/complex_kernel.h +2 -2
  25. data/ext/cumo/include/cumo/types/robj_macro.h +0 -7
  26. data/ext/cumo/include/cumo.h +2 -2
  27. data/ext/cumo/narray/array.c +1 -1
  28. data/ext/cumo/narray/data.c +45 -10
  29. data/ext/cumo/narray/data_kernel.cu +4 -0
  30. data/ext/cumo/narray/gen/spec.rb +2 -1
  31. data/ext/cumo/narray/gen/tmpl/accum.c +1 -0
  32. data/ext/cumo/narray/gen/tmpl/accum_arg.c +1 -0
  33. data/ext/cumo/narray/gen/tmpl/accum_binary_kernel.cu +13 -13
  34. data/ext/cumo/narray/gen/tmpl/accum_index.c +31 -4
  35. data/ext/cumo/narray/gen/tmpl/alloc_func.c +20 -6
  36. data/ext/cumo/narray/gen/tmpl/allocate.c +9 -0
  37. data/ext/cumo/narray/gen/tmpl/aref.c +6 -0
  38. data/ext/cumo/narray/gen/tmpl/aset.c +1 -1
  39. data/ext/cumo/narray/gen/tmpl/binary.c +11 -5
  40. data/ext/cumo/narray/gen/tmpl/binary2.c +24 -12
  41. data/ext/cumo/narray/gen/tmpl/binary2_kernel.cu +17 -3
  42. data/ext/cumo/narray/gen/tmpl/binary_kernel.cu +17 -4
  43. data/ext/cumo/narray/gen/tmpl/binary_s_kernel.cu +1 -0
  44. data/ext/cumo/narray/gen/tmpl/bincount.c +52 -10
  45. data/ext/cumo/narray/gen/tmpl/clip.c +70 -33
  46. data/ext/cumo/narray/gen/tmpl/clip_kernel.cu +58 -0
  47. data/ext/cumo/narray/gen/tmpl/complex_accum_kernel.cu +12 -4
  48. data/ext/cumo/narray/gen/tmpl/cond_binary_kernel.cu +1 -0
  49. data/ext/cumo/narray/gen/tmpl/ewcomp_kernel.cu +1 -0
  50. data/ext/cumo/narray/gen/tmpl/eye_kernel.cu +1 -0
  51. data/ext/cumo/narray/gen/tmpl/fill_kernel.cu +2 -0
  52. data/ext/cumo/narray/gen/tmpl/float_accum_kernel.cu +8 -0
  53. data/ext/cumo/narray/gen/tmpl/format.c +9 -1
  54. data/ext/cumo/narray/gen/tmpl/format_to_a.c +1 -1
  55. data/ext/cumo/narray/gen/tmpl/frexp.c +1 -0
  56. data/ext/cumo/narray/gen/tmpl/gemm.c +8 -2
  57. data/ext/cumo/narray/gen/tmpl/logseq.c +2 -1
  58. data/ext/cumo/narray/gen/tmpl/logseq_kernel.cu +2 -0
  59. data/ext/cumo/narray/gen/tmpl/median.c +1 -0
  60. data/ext/cumo/narray/gen/tmpl/minmax.c +9 -2
  61. data/ext/cumo/narray/gen/tmpl/new_dim0.c +8 -0
  62. data/ext/cumo/narray/gen/tmpl/new_dim0_kernel.cu +3 -0
  63. data/ext/cumo/narray/gen/tmpl/poly.c +9 -8
  64. data/ext/cumo/narray/gen/tmpl/pow_kernel.cu +2 -0
  65. data/ext/cumo/narray/gen/tmpl/rand.c +43 -14
  66. data/ext/cumo/narray/gen/tmpl/rand_kernel.cu +97 -0
  67. data/ext/cumo/narray/gen/tmpl/rand_norm.c +28 -57
  68. data/ext/cumo/narray/gen/tmpl/rand_norm_kernel.cu +73 -0
  69. data/ext/cumo/narray/gen/tmpl/real_accum_kernel.cu +43 -13
  70. data/ext/cumo/narray/gen/tmpl/seq.c +1 -1
  71. data/ext/cumo/narray/gen/tmpl/seq_kernel.cu +2 -0
  72. data/ext/cumo/narray/gen/tmpl/set2.c +1 -0
  73. data/ext/cumo/narray/gen/tmpl/store_array.c +20 -29
  74. data/ext/cumo/narray/gen/tmpl/store_array_kernel.cu +4 -0
  75. data/ext/cumo/narray/gen/tmpl/store_bit_kernel.cu +4 -0
  76. data/ext/cumo/narray/gen/tmpl/store_from_kernel.cu +4 -0
  77. data/ext/cumo/narray/gen/tmpl/unary.c +49 -34
  78. data/ext/cumo/narray/gen/tmpl/unary_kernel.cu +37 -15
  79. data/ext/cumo/narray/gen/tmpl/unary_ret2.c +1 -0
  80. data/ext/cumo/narray/gen/tmpl/unary_s_kernel.cu +4 -0
  81. data/ext/cumo/narray/gen/tmpl_bit/allocate.c +1 -0
  82. data/ext/cumo/narray/gen/tmpl_bit/aref.c +6 -0
  83. data/ext/cumo/narray/gen/tmpl_bit/bit_count_kernel.cu +4 -0
  84. data/ext/cumo/narray/gen/tmpl_bit/format.c +1 -1
  85. data/ext/cumo/narray/gen/tmpl_bit/format_to_a.c +1 -1
  86. data/ext/cumo/narray/gen/tmpl_bit/store_array.c +9 -10
  87. data/ext/cumo/narray/index.c +48 -18
  88. data/ext/cumo/narray/index_kernel.cu +11 -0
  89. data/ext/cumo/narray/narray.c +105 -29
  90. data/ext/cumo/narray/ndloop.c +64 -47
  91. data/ext/cumo/narray/ndloop_kernel.cu +2 -0
  92. data/ext/cumo/narray/rand.c +34 -2
  93. data/lib/cumo/cuda/nvrtc_program.rb +3 -1
  94. data/lib/cumo/narray/extra.rb +55 -13
  95. data/test/bit_test.rb +16 -0
  96. data/test/cuda/driver_test.rb +43 -0
  97. data/test/cuda/memory_pool_test.rb +123 -0
  98. data/test/cuda/nvrtc_test.rb +14 -0
  99. data/test/cumo_test.rb +35 -0
  100. data/test/extra_test.rb +1095 -0
  101. data/test/math_test.rb +638 -0
  102. data/test/narray_alt_coverage_test.rb +856 -0
  103. data/test/narray_test.rb +1070 -0
  104. metadata +13 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 324cdfec0cf163d40b3b3b635fa69ecf516aa12e15a0bf614cbc34d3fe61a4c7
4
- data.tar.gz: 3d0f6c8b66a9b51eca9fe6912c1a4e7c3534a028ed4c8d740467ae8d6e215824
3
+ metadata.gz: 12027799f8203c1e08409de1fdec1b550e26511547bbaee2a938488ecd19e079
4
+ data.tar.gz: 399daea2a183febf70ceeb1e6492479fd3a34ae29e994265c4da9f89644c3c4d
5
5
  SHA512:
6
- metadata.gz: 9c82a985c471260943c4716630d6db803706ac50cccd5e1fc8a0a2bb02d80172ec38743f5c5f286467e97814843b8fbe1d6ec1f98e75081364fd9a5c0a4da832
7
- data.tar.gz: d3ed8c3e8844d51676c4011139faa509de8cdb39ef325d84b4b8b41f6725fb63b9893fae8dbced5e546810ff4eb570f60a347ef248323f805bffa9fde60faab1
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
- task :ctest do
16
- sh 'cd ext/cumo && ruby extconf.rb && make && make build-ctest && make run-ctest'
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)
@@ -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 = malloc(sizeof(cublasHandle_t) * device_count);
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
- cublasCreate(&handles[device]);
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
  }
@@ -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 = malloc(sizeof(cudnnHandle_t) * device_count);
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
- cudnnCreate(&handles[device]);
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
  }
@@ -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)NUM2SIZET(state);
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)NUM2SIZET(state);
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)NUM2SIZET(hmod);
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
- cuDeviceGet(&cuDevice, 0);
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
- cuCtxCreate(&context, NULL, 0, cuDevice);
458
+ cuCtxCreate(&context, NULL, 0, cuDevice);
442
459
  #else
443
- cuCtxCreate(&context, 0, cuDevice);
460
+ cuCtxCreate(&context, 0, cuDevice);
444
461
  #endif
462
+ }
445
463
  }