ndav 0.0.2 → 0.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d1ffc6b4d3e19aa1f6abab6aa9e16c03cb9078c4567fa7451305523d2b5f660
4
- data.tar.gz: f4a8be1e3d8981870385e027f4b665fe305858dce2d38119b3222e98a3770bbb
3
+ metadata.gz: e27d78e17f22830ab47cba80120c7fa3ad3139690dd276e1267abb8696c6ffe3
4
+ data.tar.gz: 1a3ab5273684a54a5920528663fa5ebcab8ab861213b622b60afe3267ff184e5
5
5
  SHA512:
6
- metadata.gz: d5acded105c270841b4d107ab8ceaed11624ec4c79cfeeded5039ca9e4336a33d33c9c0a8d5d640a9e841084895a715e914c60e3f16d0e7b7bd64dfa0b2f0447
7
- data.tar.gz: ec9c3d18e521385acab9c156cf36e74f2271735759d8ce3b00bb351682187e4900382d01611ebe498e38488be868898969ec48186e7106267105495424e94cd5
6
+ metadata.gz: 6fe639a15fd780e5add9dcf124f3222214f6dd82cef28e4c16589f0f9dae936dda08e378ed28a3fa9bb810933250caf48bad2a18885a6261685451b3faf14bf0
7
+ data.tar.gz: 3b3769ce7d12622bd231d0f4bffeb590b631a4c6be42984e2c998c49b64d2c24499bf922744d11b02c27dd308735c8f29d3385c70d5f3753509ce3bd70d4415f
data/.gitignore CHANGED
@@ -6,3 +6,5 @@ ext/Makefile
6
6
  *.o
7
7
  *.bundle.dSYM
8
8
  pkg/
9
+ doc/
10
+ .yardoc/
data/.gitlab-ci.yml CHANGED
@@ -12,3 +12,15 @@ test:
12
12
  - /root/.local/share/gem/ruby
13
13
  script:
14
14
  - rake test
15
+
16
+ pages:
17
+ stage: deploy
18
+ image: ruby:4.0
19
+ script:
20
+ - bundle exec rake yard
21
+ - mv doc public
22
+ artifacts:
23
+ paths:
24
+ - public
25
+ only:
26
+ - main
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ --asset NDAV.png
2
+ -
3
+ LICENSE.txt
data/NDAV.png ADDED
Binary file
data/README.md CHANGED
@@ -1,9 +1,173 @@
1
1
  NDAV - N-Dimensional Array View
2
2
  ===============================
3
3
 
4
- Wrapper for MemoryView and pointer.
4
+ [![Gem Version](https://badge.fury.io/rb/ndav.svg)](https://badge.fury.io/rb/ndav)
5
+
6
+ A thin wrapper around [MemoryView][] ("buffer protocol" for Ruby).
7
+
8
+ It provides an interoperability layer for multi-dimensional arrays which can be shared between libraries.
9
+
10
+ ![NDAV converts library data each other](./NDAV.png)
11
+
12
+ SYNOPSIS
13
+ --------
14
+
15
+ waveform, sample_rate = TorchAudio.load("path/to/audio.wav")
16
+
17
+ # Convert Torch::Tensor to NDAV
18
+ # so that you can convert it to OrtValue,
19
+ # a data format for ONNX Runtime
20
+ input = waveform
21
+ .to_ndav
22
+ .to_ort_value
23
+
24
+ # Make ONNX Runtime return result as OrtValue
25
+ outputs = OnnxRuntime::Session.new("path/to/model.onnx")
26
+ .run(
27
+ [:output_name],
28
+ {input_name: input},
29
+ output_type: :ort_value
30
+ )
31
+
32
+ # You may convert OrtValue to Torch::Tensor via NDAV
33
+ output_tensor = outputs[0]
34
+ .to_ndav
35
+ .to_torch_tensor # converts back to Torch::Tensor
36
+
37
+ TorchAudio.save("path/to/output.wav", output_tensor, sample_rate)
38
+
39
+ ABSTRACT
40
+ --------
41
+
42
+ NDAV acts as an interoperability layer between multi-dimensional arrays including images, audio and tensors such as [Numo::NArray][], [Torch.rb][]'s `Torch::Tensor`, [ONNX Runtime Ruby][]'s `OnnxRuntime::OrtValue`, [Red Arrow][]'s `Arrow::Array` and so on.
43
+
44
+ It allows data to be shared without copying.
45
+
46
+ BACKGROUND
47
+ ----------
48
+
49
+ In the modern Ruby community, [Numo::NArray][] is often used for data conversion. But, data are copied when converting to and from Numo::NArray. In addition, Numo::NArray neither exports nor accepts MemoryView.
50
+
51
+ [Red Arrow][] is also used and it can export MemoryView from `Arrow::Array` (not from `Arrow::Tensor`, though). It also can be converted to and from Numo::NArray using [Red Arrow Numo::NArray][]. But, in real-world usage, we need, for example, to convert data with many hops:
52
+
53
+ Torch::Tensor -> Numo::NArray -> Red Arrow -> MemoryView -> some process...
54
+
55
+ It might not be difficult, but a little bit cumbersome. Additionally, Red Arrow doesn't accept MemoryView.
56
+
57
+ USAGE
58
+ -----
59
+
60
+ `ndav` gem is just a base library. You need to install bridges as well. Say, assume you want to make conversions between Numo::NArray each other.
61
+
62
+ require "numo/narray"
63
+ require "ndav"
64
+ require "ndav/numo/narray"
65
+
66
+ numo = Numo::SFloat.new(3, 5).seq # => Numo::SFloat
67
+ ndav = numo.to_ndav # => NDAV
68
+
69
+ numo = Numo::SFloat.from_ndav(ndav) # => Numo::SFloat
70
+ ndav = NDAV.from_numo_narray(numo) # => NDAV
71
+
72
+ include NDAV::Converter
73
+ numo = NumoNArray(ndav) #=> Numo::SFloat
74
+ ndav = NDAV(numo) # => NDAV
75
+
76
+ For `Torch::Tensor` and `OnnxRuntime::OrtValue`, you can do the same operation, therefore you may convert them to each other, like this:
77
+
78
+ numo
79
+ .to_ndav
80
+ .to_torch_tensor
81
+ .then {|torch_tensor| some_process(torch_tensor)}
82
+ .to_ndav
83
+ .to_ort_value
84
+ .then {|ort_value|
85
+ OnnxRuntime::Session.new("model.onnx")
86
+ .run(
87
+ [:output],
88
+ {input: ort_value},
89
+ output_type: :ort_value
90
+ )[0]
91
+ }
92
+ .to_ndav
93
+ .to_torch_tensor
94
+ .then {|torch_tensor| TorchAudio.save(torch_tensor, sample_rate)}
95
+
96
+ ### Working With MemoryView ###
97
+
98
+ NDAV can be initialized *directly* from libraries which export [MemoryView][] such as [Red Arrow][], without any bridge library:
99
+
100
+ arrow = Arrow::Int16Array.new([1, 2, 3])
101
+ ndav = NDAV.new(arrow)
102
+
103
+ On the other hand, it also exports MemoryView. You can pass NDAV arrays *directly* to methods which accept MemoryView such as whispercpp without bridge libraries:
104
+
105
+ waveform, sample_rate = TorchAudio.load("path/to/audio.wav")
106
+ samples = waveform.to_ndav
107
+ whisper.full(params, samples)
108
+
109
+ ### Notice On Memory Sharing ###
110
+
111
+ Notice that NDAV is just a memory view and libraries share a memory address. If you change source data destructively, it affects converted data.
112
+
113
+ Additionally, you potentially encounter odd data corruption or segmentation fault. These might be bugs in bridge libraries such as [ndav-numo-narray][]. As a user, you don't need to worry about this kind of memory management, but it's worth knowing such situations may occur.
114
+
115
+ INSTALLATION
116
+ ------------
117
+
118
+ % gem install ndav
119
+
120
+ or,
121
+
122
+ % bundle add ndav
123
+
124
+ But, you need bridges for real-world use. See each bridge's documentation for individual requirements.
125
+
126
+ BRIDGES
127
+ -------
128
+
129
+ There are some bridges using NDAV:
130
+
131
+ * [ndav/ffi][]: [`FFI::MemoryPointer`][FFI Pointer], [`FFI::Pointer`][FFI Pointer] <-> `NDAV`
132
+ * [ndav-numo-narray][]: [`Numo::NArray`][Numo::NArray] <-> `NDAV`
133
+ * [ndav-ort_value][]: [`OnnxRuntime::OrtValue`][ONNX Runtime Ruby] <-> `NDAV`
134
+ * [ndav-torch-tensor][]: [`Torch::Tensor`][Torch.rb] <-> `NDAV`
135
+ * [onnxruntime-torch-tensor][]: [`Torch::Tensor`][Torch.rb] <-> [`OnnxRuntime::OrtValue`][ONNX Runtime Ruby] via `NDAV`
136
+
137
+ CREATING BRIDGES
138
+ ----------------
139
+
140
+ Refer to existing bridge implementations listed above to create your bridge.
141
+
142
+ The points are:
143
+
144
+ * Implement `FromNDAV#from_ndav`, `ToNDAV#to_ndav` and `NDAV.register` them, and `NDAV.from_your_data` and `NDAV#to_your_data` are automatically derived
145
+ * When initializing NDAV object from your object, use `lifetime` keyword argument for `NDAV.new` effectively to prevent Ruby from GCing your object, which would lead to a dangling pointer
146
+ * When initializing your object from NDAV object, keep NDAV object alive to prevent Ruby from GCing NDAV object, which would lead to a dangling pointer, [ndav-numo-narray][], for instance, embeds the NDAV object in an instance variable
147
+
148
+ An advantage of NDAV over raw MemoryView is that you can write bridges in pure Ruby in most cases. It helps prototyping and experimentation. As an exception, I had to write C code for ndav-numo-narray because Numo::NArray only provides methods that access data by copying and does not directly expose its raw data pointer to Ruby API. However, in even such case, pure Ruby bridge remains a viable option for prototyping and experiments where a single initial copy is acceptable.
149
+
150
+ If you are a library author, I want you to consider making your library work with [MemoryView][] instead of creating an NDAV bridge.
151
+
152
+ FUTURE
153
+ ------
154
+
155
+ If [MemoryView][] gets popular enough in the Ruby ecosystem, this library will end its role and will no longer be needed. I hope such future.
5
156
 
6
157
  LICENSE
7
158
  -------
8
159
 
9
160
  BSD-2-Clause license. See LICENSE.txt file.
161
+
162
+ [MemoryView]: https://docs.ruby-lang.org/en/master/contributing/memory_view_md.html
163
+ [Numo::NArray]: https://ruby-numo.github.io/narray/
164
+ [Torch.rb]: https://github.com/ankane/torch.rb
165
+ [ONNX Runtime Ruby]: https://github.com/ankane/onnxruntime-ruby
166
+ [Red Arrow]: https://github.com/apache/arrow/tree/main/ruby
167
+ [Red Arrow Numo::NArray]: https://github.com/red-data-tools/red-arrow-numo-narray
168
+ [ndav/ffi]: https://gitlab.com/KitaitiMakoto/ndav/-/blob/main/lib/ndav/ffi.rb?ref_type=heads
169
+ [FFI Pointer]: https://github.com/ffi/ffi/wiki/Pointers
170
+ [ndav-numo-narray]: https://gitlab.com/KitaitiMakoto/ndav-numo-narray
171
+ [ndav-ort_value]: https://gitlab.com/KitaitiMakoto/ndav-ort_value
172
+ [ndav-torch-tensor]: https://gitlab.com/KitaitiMakoto/ndav-torch-tensor
173
+ [onnxruntime-torch-tensor]: https://gitlab.com/KitaitiMakoto/onnxruntime-torch-tensor
data/Rakefile CHANGED
@@ -1,18 +1,20 @@
1
1
  require "rake/clean"
2
2
  require "rake/testtask"
3
3
  require "rubygems/tasks"
4
+ require "yard"
4
5
 
5
6
  DL_NAME = "ndav".ext(RbConfig::CONFIG["DLEXT"])
6
7
  DL_BUILD_PATH = File.join("ext", DL_NAME)
7
8
  DL_PATH = File.join("lib", DL_NAME)
8
9
 
9
10
  SRC = FileList["ext/**.{h,c,rb}"]
11
+ CLEAN.include SRC.select {|src| src.end_with?(".o", ".so", ".bundle", ".dll") }
10
12
 
11
13
  task default: :test
12
14
 
13
15
  Rake::TestTask.new test: DL_PATH
14
- tasks = Gem::Tasks.new
15
- gemspec = tasks.build.gem.project.gemspec
16
+ Gem::Tasks.new
17
+ YARD::Rake::YardocTask.new
16
18
 
17
19
  file DL_PATH => DL_BUILD_PATH do |t|
18
20
  copy t.source, t.name
@@ -3,6 +3,33 @@ class NDAV
3
3
  alias from_memory_view new
4
4
  alias from_fiddle_memory_view new
5
5
  alias from_fiddle_pointer new
6
+
7
+ def from_string(str, *, **)
8
+ new(Fiddle::Pointer[str], *, **)
9
+ end
10
+
11
+ def register(cls, mdl, name:)
12
+ if mdl.const_defined?(:FromNDAV)
13
+ cls.extend mdl::FromNDAV
14
+
15
+ define_method "to_#{name}" do |*args, **kwargs|
16
+ cls.from_ndav(self, *args, **kwargs)
17
+ end
18
+ end
19
+
20
+ if mdl.const_defined?(:ToNDAV)
21
+ cls.include mdl::ToNDAV
22
+
23
+ define_singleton_method "from_#{name}" do |array, *args, **kwargs|
24
+ array.to_ndav(*args, **kwargs)
25
+ end
26
+ end
27
+
28
+ if mdl.const_defined?(:Converter)
29
+ Converter.prepend mdl::Converter
30
+ Converter.singleton_class.prepend mdl::Converter
31
+ end
32
+ end
6
33
  end
7
34
 
8
35
  module Converter
data/lib/ndav/ffi.rb CHANGED
@@ -18,17 +18,24 @@ class NDAV
18
18
  end
19
19
 
20
20
  module ToNDAV
21
- def to_ndav(format: nil, lifetime: nil, **)
22
- ::NDAV.from_ffi_memory_pointer(self, shape: [size / type_size], format: format || TYPE_SIZE_TO_FORMAT[type_size], lifetime: lifetime || self, **)
21
+ def to_ndav(format: TYPE_SIZE_TO_FORMAT[type_size], lifetime: self, **)
22
+ shape = [size / type_size]
23
+ byte_size = shape.reduce(ITEM_SIZES[format], :*)
24
+ ptr = ::Fiddle::Pointer.new(address, byte_size)
25
+ ::NDAV.new(ptr, shape:, format:, lifetime:, **)
23
26
  end
24
27
  end
25
28
 
26
29
  module Converter
27
- def NDAV(array, *, **)
28
- if array.kind_of? ::FFI::MemoryPointer
29
- array.to_ndav(**)
30
+ def FFIMemoryPointer(array, *, **)
31
+ if ::FFI::MemoryPointer === array
32
+ array
33
+ elsif array.respond_to? :to_ffi_memory_pointer
34
+ array.to_ffi_memory_pointer(*, **)
35
+ elsif array.respond_to? :to_ndav
36
+ array.to_ndav(*, **).to_ffi_memory_pointer
30
37
  else
31
- super
38
+ ::NDAV.new(array, *, **).to_ffi_memory_pointer
32
39
  end
33
40
  end
34
41
  end
@@ -41,53 +48,31 @@ class NDAV
41
48
  end
42
49
  end
43
50
 
44
- module ToDAV
45
- def to_ndav(format: nil, lifetime: nil, **)
46
- ::NDAV.from_ffi_pointer(self, shape: [size / type_size], format: format || TYPE_SIZE_TO_FORMAT[type_size], lifetime: lifetime || self, **)
51
+ module ToNDAV
52
+ def to_ndav(shape:, format: TYPE_SIZE_TO_FORMAT[type_size], lifetime: self, **)
53
+ byte_size = shape.reduce(ITEM_SIZES[format], :*)
54
+ ptr = ::Fiddle::Pointer.new(address, byte_size)
55
+ ::NDAV.new(ptr, shape:, format:, lifetime:, **)
47
56
  end
48
57
  end
49
58
 
50
59
  module Converter
51
- def NDAV(array, *, **)
52
- # use instance_of? instead of kind_of? because MemoryPointer < Pointer
60
+ def FFIPointer(array, *, **)
61
+ # use instance_of? instead of kind_of? beacuase MemoryPointer is subclass of Pointer
53
62
  if array.instance_of? ::FFI::Pointer
54
- array.to_ndav(**)
63
+ array
64
+ elsif array.respond_to? :to_ffi_pointer
65
+ array.to_ffi_pointer(*, **)
66
+ elsif array.respond_to? :to_ndav
67
+ array.to_ndav(*, **).to_ffi_pointer
55
68
  else
56
- super
69
+ ::NDAV.new(array, *, **).to_ffi_pointer
57
70
  end
58
71
  end
59
72
  end
60
73
  end
61
74
 
62
- module FromFFI
63
- def from_ffi_memory_pointer(array, shape:, format:, **)
64
- byte_size = shape.reduce(ITEM_SIZES[format], :*)
65
- ptr = ::Fiddle::Pointer.new(array.address, byte_size)
66
- new(ptr, shape:, format:, **)
67
- end
68
-
69
- def from_ffi_pointer(array, lifetime:, **)
70
- from_ffi_memory_pointer(array, lifetime:, **)
71
- end
72
- end
73
-
74
- module ToFFI
75
- def to_ffi_memory_pointer
76
- ::FFI::MemoryPointer.from_ndav(self)
77
- end
78
-
79
- def to_ffi_pointer
80
- ::FFI::Pointer.from_ndav(self)
81
- end
82
- end
83
-
84
- ::FFI::MemoryPointer.extend MemoryPointer::FromNDAV
85
- ::FFI::MemoryPointer.include MemoryPointer::ToNDAV
86
- ::FFI::Pointer.extend Pointer::FromNDAV
87
- ::FFI::Pointer.include Pointer::ToDAV
88
- ::NDAV.extend FromFFI
89
- ::NDAV.include ToFFI
90
- ::NDAV::Converter.singleton_class.prepend Pointer::Converter
91
- ::NDAV::Converter.singleton_class.prepend MemoryPointer::Converter
75
+ ::NDAV.register ::FFI::MemoryPointer, MemoryPointer, name: :ffi_memory_pointer
76
+ ::NDAV.register ::FFI::Pointer, Pointer, name: :ffi_pointer
92
77
  end
93
78
  end
data/ndav.gemspec CHANGED
@@ -1,10 +1,11 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "ndav"
3
- s.version = "0.0.2"
3
+ s.version = "0.0.4"
4
4
  s.summary = "N-Dimensional Array View"
5
5
  s.authors = ["Kitaiti Makoto"]
6
6
  s.licenses = ["BSD-2-Clause"]
7
- s.homepage = "https://gitlab.com/KitaitiMakoto/ndav"
7
+ s.homepage = "https://kitaitimakoto.gitlab.io/ndav"
8
+ s.metadata["source_code_uri"] = "https://gitlab.com/KitaitiMakoto/ndav"
8
9
 
9
10
  s.files = Dir.chdir(__dir__) {`git ls-files -z`.split("\x0")}
10
11
  s.extensions = ["ext/extconf.rb"]
@@ -20,4 +21,5 @@ Gem::Specification.new do |s|
20
21
  s.add_development_dependency "rubygems-requirements-system"
21
22
  s.add_development_dependency "red-arrow"
22
23
  s.add_development_dependency "ffi"
24
+ s.add_development_dependency "yard"
23
25
  end
data/test/test_ffi.rb CHANGED
@@ -57,6 +57,26 @@ class TestFFI < TestBase
57
57
  assert_int_array ndav
58
58
  end
59
59
 
60
+ def test_ffi_memory_pointer_from_ndav
61
+ ndav = ::NDAV.new(Arrow::Int16Array.new([1, 2, 3]))
62
+
63
+ assert_instance_of ::FFI::MemoryPointer, ::NDAV::Converter::FFIMemoryPointer(ndav)
64
+ end
65
+
66
+ def test_FFIMemoryPointer
67
+ c = Class.new {
68
+ include ::NDAV::Converter
69
+
70
+ def to_ffi_memory_pointer(ndav)
71
+ FFIMemoryPointer(ndav)
72
+ end
73
+ }
74
+
75
+ ndav = ::NDAV.new(Arrow::Int16Array.new([1, 2, 3]))
76
+
77
+ assert_instance_of ::FFI::MemoryPointer, c.new.to_ffi_memory_pointer(ndav)
78
+ end
79
+
60
80
  def test_ndav_from_ffi_pointer
61
81
  str = [1, 2, 3].pack("s*")
62
82
  address = ::Fiddle::Pointer[str].to_i
@@ -65,5 +85,25 @@ class TestFFI < TestBase
65
85
 
66
86
  assert_int_array ndav
67
87
  end
88
+
89
+ def test_ffi_pointer_from_ndav
90
+ ndav = ::NDAV.new(Arrow::Int16Array.new([1, 2, 3]))
91
+
92
+ assert_instance_of ::FFI::Pointer, ::NDAV::Converter::FFIPointer(ndav)
93
+ end
94
+
95
+ def test_FFIPointer
96
+ c = Class.new {
97
+ include ::NDAV::Converter
98
+
99
+ def to_ffi_pointer(ndav)
100
+ FFIPointer(ndav)
101
+ end
102
+ }
103
+
104
+ ndav = ::NDAV.new(Arrow::Int16Array.new([1, 2, 3]))
105
+
106
+ assert_instance_of ::FFI::Pointer, c.new.to_ffi_pointer(ndav)
107
+ end
68
108
  end
69
109
  end
data/test/test_ndav.rb CHANGED
@@ -28,4 +28,11 @@ class TestNDAV < TestBase
28
28
  assert_equal ptr, ndav.to_fiddle_pointer
29
29
  assert_int_array ndav
30
30
  end
31
+
32
+ def test_string
33
+ str = [1, 2, 3].pack("s*")
34
+ ndav = NDAV.from_string(str, format: "s")
35
+
36
+ assert_int_array ndav
37
+ end
31
38
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ndav
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kitaiti Makoto
@@ -149,6 +149,20 @@ dependencies:
149
149
  - - ">="
150
150
  - !ruby/object:Gem::Version
151
151
  version: '0'
152
+ - !ruby/object:Gem::Dependency
153
+ name: yard
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ type: :development
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
152
166
  executables: []
153
167
  extensions:
154
168
  - ext/extconf.rb
@@ -156,8 +170,10 @@ extra_rdoc_files: []
156
170
  files:
157
171
  - ".gitignore"
158
172
  - ".gitlab-ci.yml"
173
+ - ".yardopts"
159
174
  - Gemfile
160
175
  - LICENSE.txt
176
+ - NDAV.png
161
177
  - README.md
162
178
  - Rakefile
163
179
  - ext/extconf.rb
@@ -173,10 +189,11 @@ files:
173
189
  - test/test_ffi.rb
174
190
  - test/test_ndav.rb
175
191
  - test/test_package.rb
176
- homepage: https://gitlab.com/KitaitiMakoto/ndav
192
+ homepage: https://kitaitimakoto.gitlab.io/ndav
177
193
  licenses:
178
194
  - BSD-2-Clause
179
- metadata: {}
195
+ metadata:
196
+ source_code_uri: https://gitlab.com/KitaitiMakoto/ndav
180
197
  rdoc_options: []
181
198
  require_paths:
182
199
  - lib