cumo 0.13.0 → 0.14.1
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 +30 -0
- data/README.md +50 -0
- data/Rakefile +29 -0
- data/cumo.gemspec +10 -1
- data/ext/cumo/cuda/cufft.c +605 -0
- data/ext/cumo/cuda/driver.c +1 -0
- data/ext/cumo/cuda/memory_pool.cpp +2 -0
- data/ext/cumo/cumo.c +2 -2
- data/ext/cumo/extconf.rb +8 -0
- data/ext/cumo/include/cumo/cuda/cufft.h +38 -0
- data/ext/cumo/include/cumo/cuda/runtime.h +3 -2
- data/ext/cumo/include/cumo/ndloop.h +7 -0
- data/ext/cumo/include/cumo.h +0 -3
- data/ext/cumo/narray/gen/tmpl/binary.c +50 -0
- data/ext/cumo/narray/gen/tmpl/binary_kernel.cu +24 -0
- data/ext/cumo/narray/gen/tmpl/map_with_index.c +18 -0
- data/ext/cumo/narray/gen/tmpl/unary.c +8 -0
- data/ext/cumo/narray/narray.c +1 -2
- data/ext/cumo/narray/ndloop.c +137 -16
- data/lib/cumo/fft.rb +482 -0
- data/lib/cumo/linalg.rb +19 -8
- data/lib/cumo/narray/extra.rb +31 -0
- data/lib/cumo/pocketfft.rb +134 -0
- data/lib/cumo/version.rb +8 -0
- data/lib/cumo.rb +1 -0
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '0872662f2f59cb4486df50f2f9b6dee5fc62fe938f6c47bafdb1ebdfc744c2f6'
|
|
4
|
+
data.tar.gz: 248f4ea1224ee0ff46069d16b424b1c90a98d81b33610506c58d45ffb9693c08
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: caf8d608d673afd9e71d3c421d2c096db503bb0054bf1ca846f33de738ccb2662ab53a6c715e6003f9a27b48d148d1e8f60f509076d53eee1b1bebf56c023e97
|
|
7
|
+
data.tar.gz: 78c2fd433b79741516f842b069adfa90c0452694a106554e99fd0ac5415684d6b82beb7f44dae63a09a745fc075bf21c1fb71276fa8ad691965f9ddd335e8d9a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
# 0.14.1 (2026/10/22)
|
|
2
|
+
|
|
3
|
+
Fixes:
|
|
4
|
+
|
|
5
|
+
* Fix a host loop such as `each` yielding the old values of an array after its block wrote the array through `store_binary`, an in-place `cumsum` or `cumprod`, or an in-place `map` or `map_with_index`, also one paused by an Enumerator (PR #631, PR #630)
|
|
6
|
+
* Fix an in-place `map_with_index` over a view that reaches an element more than once, such as `x[[0, 0, 0]]`, reading the element as it was before its own write. It now reads its own write, as `map` and numo do (PR #631)
|
|
7
|
+
* Fix the release gem recording the tag of the previous release as `Cumo::REVISION`, as the 0.14.0 gem does with `"v0.13.0-11-gafd8ead9"`. `rake release` now makes the tag before it builds the gem, so this gem gives `"v0.14.1"` (PR #626)
|
|
8
|
+
|
|
9
|
+
Changes:
|
|
10
|
+
|
|
11
|
+
* `+`, `-`, `*` and `/` between a complex array and a real array of the same precision, in either order, read the real array as it is rather than casting it to a complex array first. On 4M elements this makes SComplex with SFloat about 1.7 to 1.8 times faster, and DComplex divided by DFloat about 1.4 times. The answers are the same but for the sign of a NaN (PR #632)
|
|
12
|
+
* A host loop such as `each` whose block writes to the device copies the rest of the row, or of the array, again at once when copying the elements one at a time would cost more. With one write early in an `each_with_index` over 100,000 elements, the loop is about 90 times faster and about as fast as with no write. With a write at every 100th element it is about 12 times faster, and with a write at every other element about 16% slower (PR #631)
|
|
13
|
+
|
|
14
|
+
# 0.14.0 (2026/10/18)
|
|
15
|
+
|
|
16
|
+
Breaking changes:
|
|
17
|
+
|
|
18
|
+
* `cumo.h` no longer defines `CUMO_VERSION` or `CUMO_VERSION_CODE`. `Cumo::VERSION` and `Cumo::NArray::VERSION` are defined in Ruby. The new `Cumo::REVISION` tells a release gem from one built from a checkout. A release gives its tag, and a gem built at a later commit gives something like `"v0.14.0-3-g1a2b3c4d"`. The 0.14.0 gem was built before its tag was made, so it gives `"v0.13.0-11-gafd8ead9"`. `rake release` makes the tag before the gem from the next release on (PR #617, PR #626)
|
|
19
|
+
|
|
20
|
+
Fixes:
|
|
21
|
+
|
|
22
|
+
* Fix a host loop such as `each_with_index` yielding the old values of an array after a kernel launched from its block through `Function#launch`, `ElementwiseKernel` or `ReductionKernel` wrote the array (PR #625)
|
|
23
|
+
|
|
24
|
+
Changes:
|
|
25
|
+
|
|
26
|
+
* Add `Cumo::FFT`, the functions of `numpy.fft` on cuFFT with numpy's arguments as keywords: `fft`, `ifft`, `rfft`, `irfft`, `hfft`, `ihfft`, `fft2`, `ifft2`, `rfft2`, `irfft2`, `fftn`, `ifftn`, `rfftn`, `irfftn`, `fftfreq`, `rfftfreq`, `fftshift` and `ifftshift`, and `fftconvolve`, which answers the full linear convolution as `scipy.signal.fftconvolve` does. Load it with `require "cumo/fft"`. Single precision stays single, as in CuPy. cuFFT is loaded the first time a transform runs, and `Cumo::CUDA::Cufft.available?` says whether it can be. A thread keeps up to 16 plans for each device, which `Cumo::CUDA::Cufft.plan_cache_info` and `clear_plan_cache` show and let go (PR #623, PR #622, PR #621, PR #620)
|
|
27
|
+
* Add `Cumo::Pocketfft`, the functions of `Numo::Pocketfft` from numo-pocketfft on `Cumo::FFT`, with the same arguments, return types and errors. Load it with `require "cumo/pocketfft"`. A 0-dimensional array raises `Cumo::NArray::DimensionError`, where numo-pocketfft fails on it in a different way in each function (PR #623)
|
|
28
|
+
* Add `Cumo::NArray.from_numo` and `Cumo::NArray.to_numo`, which copy an array between Numo and Cumo and keep its type, where `Cumo::NArray.cast` read the elements one by one through a Ruby Array and turned an SFloat into a DFloat (closes issue #87, PR #619)
|
|
29
|
+
* `Cumo::Linalg.expm` of a dense matrix that needs squaring skips the check for a triangular matrix that 0.13.0 added, which makes a 64 x 64 expm about 8% faster in SFloat and 6% in DFloat (PR #618)
|
|
30
|
+
|
|
1
31
|
# 0.13.0 (2026/10/12)
|
|
2
32
|
|
|
3
33
|
Breaking changes:
|
data/README.md
CHANGED
|
@@ -97,6 +97,24 @@ end
|
|
|
97
97
|
a = xm::DFloat.new(3,5).seq
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
### Moving Arrays Between Numo And Cumo
|
|
101
|
+
|
|
102
|
+
`Cumo::NArray.from_numo` copies a Numo array to the GPU, and `Cumo::NArray.to_numo` copies a Cumo array back to the host.
|
|
103
|
+
The type keeps its name, so a `Numo::Int32` becomes a `Cumo::Int32` and back.
|
|
104
|
+
Both copy the bytes, where `Cumo::NArray.cast` would read the elements one by one through a Ruby Array.
|
|
105
|
+
A byte-swapped array arrives in host order.
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
require 'numo/narray'
|
|
109
|
+
require 'cumo/narray'
|
|
110
|
+
|
|
111
|
+
a = Cumo::NArray.from_numo(Numo::SFloat.new(3,5).seq) #=> Cumo::SFloat
|
|
112
|
+
b = Cumo::NArray.to_numo(a * 2) #=> Numo::SFloat
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Cumo does not depend on Numo. `to_numo` requires `numo/narray` when it is called, so Numo has to be installed for it.
|
|
116
|
+
`HFloat`, `BFloat` and `RObject` raise `TypeError`, since Numo has no half-precision types and an `RObject` holds Ruby objects rather than bytes.
|
|
117
|
+
|
|
100
118
|
### Incompatibility With Numo
|
|
101
119
|
|
|
102
120
|
Numo returns a Ruby numeric object wherever a result is 0-dimensional, while Cumo returns the 0-dimensional NArray itself.
|
|
@@ -318,6 +336,38 @@ A function waits for the current stream when it answers a Ruby number, or when i
|
|
|
318
336
|
`solve`, `inv` and `det` read back whether the matrix was singular, `cho_fact` whether it was positive definite, and `eigh` and `svd` whether they converged.
|
|
319
337
|
`matmul`, `dot` of two matrices, `matrix_power` to a positive power, `cholesky`, `cho_solve`, `qr` and `norm` along an axis wait for nothing, so a run of them is queued without the host waiting.
|
|
320
338
|
|
|
339
|
+
### Fourier Transforms
|
|
340
|
+
|
|
341
|
+
`require "cumo/fft"` brings in `Cumo::FFT`, the functions of `numpy.fft` on the GPU through cuFFT.
|
|
342
|
+
They take numpy's arguments as keywords: `n:`, `axis:` and `norm:` for the one-dimensional transforms, and `s:`, `axes:` and `norm:` for the others.
|
|
343
|
+
|
|
344
|
+
```ruby
|
|
345
|
+
require "cumo/fft"
|
|
346
|
+
|
|
347
|
+
x = Cumo::SFloat.new(1024).rand
|
|
348
|
+
spectrum = Cumo::FFT.rfft(x)
|
|
349
|
+
back = Cumo::FFT.irfft(spectrum, n: 1024)
|
|
350
|
+
image = Cumo::FFT.fft2(Cumo::DComplex.new(256, 256).rand, norm: "ortho")
|
|
351
|
+
freqs = Cumo::FFT.fftfreq(1024, d: 0.01)
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
The functions are `fft`, `ifft`, `rfft`, `irfft`, `hfft`, `ihfft`, `fft2`, `ifft2`, `rfft2`, `irfft2`, `fftn`, `ifftn`, `rfftn`, `irfftn`, `fftfreq`, `rfftfreq`, `fftshift` and `ifftshift`, as in numpy.
|
|
355
|
+
`fftconvolve(a, b)` answers the full linear convolution, as `scipy.signal.fftconvolve` does in its `"full"` mode, and pads each axis to a length cuFFT computes fast on the way.
|
|
356
|
+
Single precision stays single: `SFloat`, `SComplex`, `HFloat` and `BFloat` give `SComplex`, or `SFloat` for a real result, and every other type gives `DComplex` or `DFloat`, as in numpy 2 and CuPy.
|
|
357
|
+
Errors follow numpy: an axis out of range raises `Cumo::NArray::DimensionError`, a transform of no points `ArgumentError`, and a complex input to `rfft` `TypeError`.
|
|
358
|
+
|
|
359
|
+
Cumo looks for the cuFFT header when it is built, and loads the library the first time a transform runs, so `require "cumo"` does not pay for it.
|
|
360
|
+
`Cumo::CUDA::Cufft.available?` answers whether it can be loaded.
|
|
361
|
+
`fftfreq`, `rfftfreq`, `fftshift` and `ifftshift` work without it.
|
|
362
|
+
|
|
363
|
+
A thread keeps up to 16 cuFFT plans for each device, and reuses one when a transform of the same type and shape comes again.
|
|
364
|
+
`Cumo::CUDA::Cufft.plan_cache_info` answers how many plans it holds and how often they were reused, and `Cumo::CUDA::Cufft.clear_plan_cache` lets them go.
|
|
365
|
+
A thread's plans also go when it ends, and when an allocation it makes from the memory pool runs out of memory.
|
|
366
|
+
|
|
367
|
+
`require "cumo/pocketfft"` brings in `Cumo::Pocketfft`, the functions of `Numo::Pocketfft` from [numo-pocketfft](https://github.com/yoshoku/numo-pocketfft).
|
|
368
|
+
They keep its single positional argument, its double precision results and the `ArgumentError`s it raises, so code written for numo-pocketfft runs on the GPU after the replacement of `Numo` with `Cumo` above.
|
|
369
|
+
A 0-dimensional array raises `Cumo::NArray::DimensionError`, where numo-pocketfft fails on it in a different way in each function.
|
|
370
|
+
|
|
321
371
|
### Fused Operations
|
|
322
372
|
|
|
323
373
|
`layer_norm`, `rms_norm` and `softmax` normalize along the last axis in one kernel each, and `quantize_symmetric` takes it to 8-bit integers in one more.
|
data/Rakefile
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
require "bundler/gem_tasks"
|
|
4
4
|
require "rake/testtask"
|
|
5
|
+
require "rubygems/package"
|
|
6
|
+
|
|
7
|
+
Rake::Task["release"].clear
|
|
8
|
+
desc "Tag the version, build the gem at the tag, and push the branch, the tag and the gem"
|
|
9
|
+
task :release, [:remote] do |_, args|
|
|
10
|
+
git = ->(*argv) { IO.popen(["git", *argv], err: File::NULL, &:read).strip }
|
|
11
|
+
version = File.read(File.join(__dir__, "lib/cumo/version.rb"))[/VERSION = "([^"]+)"/, 1]
|
|
12
|
+
tag = "v#{version}"
|
|
13
|
+
gem_file = "pkg/cumo-#{version}.gem"
|
|
14
|
+
remote = args[:remote] || "origin"
|
|
15
|
+
raise "There are files that need to be committed first." unless system("git", "diff", "--quiet") && system("git", "diff-index", "--quiet", "--cached", "HEAD")
|
|
16
|
+
|
|
17
|
+
tagged = git.call("rev-parse", "-q", "--verify", "#{tag}^{commit}")
|
|
18
|
+
raise "#{tag} is on #{tagged[0, 8]}, not on HEAD." unless tagged.empty? || tagged == git.call("rev-parse", "HEAD")
|
|
19
|
+
|
|
20
|
+
sh "git", "tag", "-m", "Version #{version}", tag if tagged.empty?
|
|
21
|
+
begin
|
|
22
|
+
mkdir_p "pkg"
|
|
23
|
+
sh "gem", "build", "cumo.gemspec", "--output", gem_file
|
|
24
|
+
revision = Gem::Package.new(gem_file).spec.metadata["cumo_revision"]
|
|
25
|
+
raise "#{gem_file} records #{revision.inspect} as cumo_revision, not #{tag.inspect}." unless revision == tag
|
|
26
|
+
rescue StandardError
|
|
27
|
+
sh "git", "tag", "-d", tag if tagged.empty?
|
|
28
|
+
raise
|
|
29
|
+
end
|
|
30
|
+
sh "git", "push", remote, "HEAD"
|
|
31
|
+
sh "git", "push", remote, "refs/tags/#{tag}"
|
|
32
|
+
sh "gem", "push", gem_file unless ENV["gem_push"] == "no"
|
|
33
|
+
end
|
|
5
34
|
|
|
6
35
|
Rake::TestTask.new(:test) do |t|
|
|
7
36
|
t.libs << "test"
|
data/cumo.gemspec
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
lib = File.expand_path("../lib", __FILE__)
|
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
# Requiring version.rb here would fix Cumo::REVISION to nil under bundle exec.
|
|
7
|
+
cumo_version = File.read(File.join(__dir__, "lib/cumo/version.rb"))[/VERSION = "([^"]+)"/, 1]
|
|
7
8
|
|
|
8
9
|
Gem::Specification.new do |spec|
|
|
9
10
|
spec.name = "cumo"
|
|
@@ -28,6 +29,14 @@ Gem::Specification.new do |spec|
|
|
|
28
29
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
29
30
|
f.start_with?(*%w[.github/ test/ examples/ bin/ bench/ docker- Dockerfile Gemfile .gitignore .rubocop])
|
|
30
31
|
end
|
|
32
|
+
|
|
33
|
+
revision = begin
|
|
34
|
+
IO.popen(%W[git -C #{__dir__} describe --tags --match v[0-9]* --dirty --always], err: File::NULL, &:read).strip
|
|
35
|
+
rescue SystemCallError
|
|
36
|
+
""
|
|
37
|
+
end
|
|
38
|
+
spec.metadata["cumo_revision"] = revision unless revision.empty?
|
|
39
|
+
|
|
31
40
|
spec.bindir = "exe"
|
|
32
41
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
33
42
|
spec.require_paths = ["lib"]
|