torch-rb 0.3.6 → 0.5.0

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.
@@ -1,3 +1,3 @@
1
1
  module Torch
2
- VERSION = "0.3.6"
2
+ VERSION = "0.5.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: torch-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.6
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-18 00:00:00.000000000 Z
11
+ date: 2020-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rice
@@ -108,7 +108,7 @@ dependencies:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
110
  version: 0.1.1
111
- description:
111
+ description:
112
112
  email: andrew@chartkick.com
113
113
  executables: []
114
114
  extensions:
@@ -118,19 +118,23 @@ files:
118
118
  - CHANGELOG.md
119
119
  - LICENSE.txt
120
120
  - README.md
121
+ - codegen/function.rb
122
+ - codegen/generate_functions.rb
123
+ - codegen/native_functions.yaml
121
124
  - ext/torch/ext.cpp
122
125
  - ext/torch/extconf.rb
123
- - ext/torch/templates.cpp
124
- - ext/torch/templates.hpp
126
+ - ext/torch/nn_functions.h
127
+ - ext/torch/ruby_arg_parser.cpp
128
+ - ext/torch/ruby_arg_parser.h
129
+ - ext/torch/templates.h
130
+ - ext/torch/tensor_functions.h
131
+ - ext/torch/torch_functions.h
132
+ - ext/torch/utils.h
133
+ - ext/torch/wrap_outputs.h
125
134
  - lib/torch-rb.rb
126
135
  - lib/torch.rb
127
136
  - lib/torch/hub.rb
128
137
  - lib/torch/inspector.rb
129
- - lib/torch/native/dispatcher.rb
130
- - lib/torch/native/function.rb
131
- - lib/torch/native/generator.rb
132
- - lib/torch/native/native_functions.yaml
133
- - lib/torch/native/parser.rb
134
138
  - lib/torch/nn/adaptive_avg_pool1d.rb
135
139
  - lib/torch/nn/adaptive_avg_pool2d.rb
136
140
  - lib/torch/nn/adaptive_avg_pool3d.rb
@@ -238,6 +242,7 @@ files:
238
242
  - lib/torch/nn/tanhshrink.rb
239
243
  - lib/torch/nn/triplet_margin_loss.rb
240
244
  - lib/torch/nn/unfold.rb
245
+ - lib/torch/nn/upsample.rb
241
246
  - lib/torch/nn/utils.rb
242
247
  - lib/torch/nn/weighted_loss.rb
243
248
  - lib/torch/nn/zero_pad2d.rb
@@ -269,7 +274,7 @@ homepage: https://github.com/ankane/torch.rb
269
274
  licenses:
270
275
  - BSD-3-Clause
271
276
  metadata: {}
272
- post_install_message:
277
+ post_install_message:
273
278
  rdoc_options: []
274
279
  require_paths:
275
280
  - lib
@@ -284,8 +289,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
284
289
  - !ruby/object:Gem::Version
285
290
  version: '0'
286
291
  requirements: []
287
- rubygems_version: 3.1.2
288
- signing_key:
292
+ rubygems_version: 3.1.4
293
+ signing_key:
289
294
  specification_version: 4
290
295
  summary: Deep learning for Ruby, powered by LibTorch
291
296
  test_files: []
@@ -1,48 +0,0 @@
1
- # We use a generic interface for methods (*args, **options)
2
- # and this class to determine the C++ method to call
3
- #
4
- # This is needed since LibTorch uses function overloading,
5
- # which isn't available in Ruby or Python
6
- #
7
- # PyTorch uses this approach, but the parser/dispatcher is written in C++
8
- #
9
- # We could generate Ruby methods directly, but an advantage of this approach is
10
- # arguments and keyword arguments can be used interchangably like in Python,
11
- # making it easier to port code
12
-
13
- module Torch
14
- module Native
15
- module Dispatcher
16
- class << self
17
- def bind
18
- functions = Generator.grouped_functions
19
- bind_functions(::Torch, :define_singleton_method, functions[:torch])
20
- bind_functions(::Torch::Tensor, :define_method, functions[:tensor])
21
- bind_functions(::Torch::NN, :define_singleton_method, functions[:nn])
22
- end
23
-
24
- def bind_functions(context, def_method, functions)
25
- functions.group_by(&:ruby_name).sort_by { |g, _| g }.each do |name, funcs|
26
- if def_method == :define_method
27
- funcs.map! { |f| Function.new(f.function) }
28
- funcs.each { |f| f.args.reject! { |a| a[:name] == "self" } }
29
- end
30
-
31
- defined = def_method == :define_method ? context.method_defined?(name) : context.respond_to?(name)
32
- next if defined && name != "clone"
33
-
34
- parser = Parser.new(funcs)
35
-
36
- context.send(def_method, name) do |*args, **options|
37
- result = parser.parse(args, options)
38
- raise ArgumentError, result[:error] if result[:error]
39
- send(result[:name], *result[:args])
40
- end
41
- end
42
- end
43
- end
44
- end
45
- end
46
- end
47
-
48
- Torch::Native::Dispatcher.bind
@@ -1,119 +0,0 @@
1
- module Torch
2
- module Native
3
- class Function
4
- attr_reader :function, :tensor_options
5
-
6
- def initialize(function)
7
- @function = function
8
-
9
- tensor_options_str = ", *, ScalarType? dtype=None, Layout? layout=None, Device? device=None, bool? pin_memory=None)"
10
- @tensor_options = @function["func"].include?(tensor_options_str)
11
- @function["func"].sub!(tensor_options_str, ")")
12
- end
13
-
14
- def func
15
- @func ||= @function["func"]
16
- end
17
-
18
- def name
19
- @name ||= func.split("(", 2).first
20
- end
21
-
22
- def python_module
23
- @python_module ||= @function["python_module"]
24
- end
25
-
26
- def variants
27
- @variants ||= (@function["variants"] || "function").split(", ")
28
- end
29
-
30
- def args
31
- @args ||= begin
32
- args = []
33
- pos = true
34
- args_str = func.split("(", 2).last.split(") ->").first
35
- args_str.split(", ").each do |a|
36
- if a == "*"
37
- pos = false
38
- next
39
- end
40
- t, _, k = a.rpartition(" ")
41
- k, d = k.split("=")
42
- has_default = !d.nil?
43
-
44
- if d
45
- d =
46
- case d
47
- when "True"
48
- true
49
- when "False"
50
- false
51
- when "None"
52
- nil
53
- when /\A\-?\d+\z/
54
- d.to_i
55
- when "[]"
56
- []
57
- when "[0,1]"
58
- [0, 1]
59
- when /\A\de\-\d+\z/, /\A\d+\.\d+\z/
60
- d.to_f
61
- when "Mean"
62
- "mean"
63
- when "contiguous_format"
64
- d
65
- when "long"
66
- :long
67
- else
68
- raise "Unknown default: #{d}"
69
- end
70
- end
71
-
72
- next if t == "Generator?"
73
- next if t == "MemoryFormat"
74
- next if t == "MemoryFormat?"
75
- args << {name: k, type: t, default: d, pos: pos, has_default: has_default}
76
- end
77
- args
78
- end
79
- end
80
-
81
- def out_size
82
- @out_size ||= func.split("->").last.count("!")
83
- end
84
-
85
- def ret_size
86
- @ret_size ||= func.split("->").last.split(", ").size
87
- end
88
-
89
- def ret_array?
90
- @ret_array ||= func.split("->").last.include?('[]')
91
- end
92
-
93
- def out?
94
- out_size > 0 && base_name[-1] != "_"
95
- end
96
-
97
- def ruby_name
98
- @ruby_name ||= begin
99
- name = base_name
100
- if name.end_with?("_")
101
- "#{name[0..-2]}!"
102
- elsif name.start_with?("is_")
103
- "#{name[3..-1]}?"
104
- else
105
- name
106
- end
107
- end
108
- end
109
-
110
- def cpp_name
111
- @cpp_name ||= "_" + name.downcase.sub(".", "_")
112
- end
113
-
114
- def base_name
115
- @base_name ||= name.split(".").first
116
- end
117
- end
118
- end
119
- end
@@ -1,168 +0,0 @@
1
- require "yaml"
2
- # use require_relative for
3
- # rake generate:function (without bundle)
4
- require_relative "function"
5
-
6
- module Torch
7
- module Native
8
- module Generator
9
- class << self
10
- def generate_cpp_functions
11
- functions = grouped_functions
12
- generate_cpp_file("torch", :define_singleton_method, functions[:torch])
13
- generate_cpp_file("tensor", :define_method, functions[:tensor])
14
- generate_cpp_file("nn", :define_singleton_method, functions[:nn])
15
- end
16
-
17
- def grouped_functions
18
- functions = functions()
19
-
20
- # skip functions
21
- skip_args = ["Layout", "Storage", "ConstQuantizerPtr"]
22
-
23
- # remove functions
24
- functions.reject! do |f|
25
- f.ruby_name.start_with?("_") ||
26
- f.ruby_name.include?("_backward") ||
27
- f.args.any? { |a| a[:type].include?("Dimname") }
28
- end
29
-
30
- # separate out into todo
31
- todo_functions, functions =
32
- functions.partition do |f|
33
- f.args.any? do |a|
34
- skip_args.any? { |sa| a[:type].include?(sa) } ||
35
- # call to 'range' is ambiguous
36
- f.cpp_name == "_range" ||
37
- # native_functions.yaml is missing size argument for normal
38
- # https://pytorch.org/cppdocs/api/function_namespacetorch_1a80253fe5a3ded4716ec929a348adb4b9.html
39
- (f.base_name == "normal" && !f.out?)
40
- end
41
- end
42
-
43
- # todo_functions.each do |f|
44
- # puts f.func
45
- # puts
46
- # end
47
-
48
- nn_functions, other_functions = functions.partition { |f| f.python_module == "nn" }
49
- torch_functions = other_functions.select { |f| f.variants.include?("function") }
50
- tensor_functions = other_functions.select { |f| f.variants.include?("method") }
51
-
52
- {torch: torch_functions, tensor: tensor_functions, nn: nn_functions}
53
- end
54
-
55
- private
56
-
57
- def generate_cpp_file(type, def_method, functions)
58
- hpp_template = <<-TEMPLATE
59
- // generated by rake generate:functions
60
- // do not edit by hand
61
-
62
- #pragma once
63
-
64
- void add_%{type}_functions(Module m);
65
- TEMPLATE
66
-
67
- cpp_template = <<-TEMPLATE
68
- // generated by rake generate:functions
69
- // do not edit by hand
70
-
71
- #include <torch/torch.h>
72
- #include <rice/Module.hpp>
73
- #include "templates.hpp"
74
-
75
- void add_%{type}_functions(Module m) {
76
- m
77
- %{functions};
78
- }
79
- TEMPLATE
80
-
81
- cpp_defs = []
82
- functions.sort_by(&:cpp_name).each do |func|
83
- fargs = func.args.dup #.select { |a| a[:type] != "Generator?" }
84
- fargs << {name: "options", type: "TensorOptions"} if func.tensor_options
85
-
86
- cpp_args = []
87
- fargs.each do |a|
88
- t =
89
- case a[:type]
90
- when "Tensor"
91
- "const Tensor &"
92
- when "Tensor?"
93
- # TODO better signature
94
- "OptionalTensor"
95
- when "ScalarType?"
96
- "torch::optional<ScalarType>"
97
- when "Tensor[]"
98
- "TensorList"
99
- when "Tensor?[]"
100
- # TODO make optional
101
- "TensorList"
102
- when "int"
103
- "int64_t"
104
- when "int?"
105
- "torch::optional<int64_t>"
106
- when "float?"
107
- "torch::optional<double>"
108
- when "bool?"
109
- "torch::optional<bool>"
110
- when "Scalar?"
111
- "torch::optional<torch::Scalar>"
112
- when "float"
113
- "double"
114
- when /\Aint\[/
115
- "IntArrayRef"
116
- when /Tensor\(\S!?\)/
117
- "Tensor &"
118
- when "str"
119
- "std::string"
120
- when "TensorOptions"
121
- "const torch::TensorOptions &"
122
- else
123
- a[:type]
124
- end
125
-
126
- t = "MyReduction" if a[:name] == "reduction" && t == "int64_t"
127
- cpp_args << [t, a[:name]].join(" ").sub("& ", "&")
128
- end
129
-
130
- dispatch = func.out? ? "#{func.base_name}_out" : func.base_name
131
- args = fargs.map { |a| a[:name] }
132
- args.unshift(*args.pop(func.out_size)) if func.out?
133
- args.delete("self") if def_method == :define_method
134
-
135
- prefix = def_method == :define_method ? "self." : "torch::"
136
-
137
- body = "#{prefix}#{dispatch}(#{args.join(", ")})"
138
-
139
- if func.ret_size > 1 || func.ret_array?
140
- body = "wrap(#{body})"
141
- end
142
-
143
- cpp_defs << ".#{def_method}(
144
- \"#{func.cpp_name}\",
145
- *[](#{cpp_args.join(", ")}) {
146
- return #{body};
147
- })"
148
- end
149
-
150
- hpp_contents = hpp_template % {type: type}
151
- cpp_contents = cpp_template % {type: type, functions: cpp_defs.join("\n ")}
152
-
153
- path = File.expand_path("../../../ext/torch", __dir__)
154
- File.write("#{path}/#{type}_functions.hpp", hpp_contents)
155
- File.write("#{path}/#{type}_functions.cpp", cpp_contents)
156
- end
157
-
158
- def functions
159
- @native_functions ||= YAML.load_file(path).map { |f| Function.new(f) }
160
- end
161
-
162
- def path
163
- File.expand_path("native_functions.yaml", __dir__)
164
- end
165
- end
166
- end
167
- end
168
- end
@@ -1,148 +0,0 @@
1
- module Torch
2
- module Native
3
- class Parser
4
- def initialize(functions)
5
- @functions = functions
6
- @name = @functions.first.ruby_name
7
- @min_args = @functions.map { |f| f.args.count { |a| a[:pos] && !a[:has_default] } }.min
8
- @max_args = @functions.map { |f| f.args.count { |a| a[:pos] } }.max
9
- end
10
-
11
- def parse(args, options)
12
- candidates = @functions.dup
13
-
14
- # remove nil
15
- while args.any? && args.last.nil?
16
- args.pop
17
- end
18
-
19
- # TODO account for args passed as options here
20
- if args.size < @min_args || args.size > @max_args
21
- expected = String.new(@min_args.to_s)
22
- expected += "..#{@max_args}" if @max_args != @min_args
23
- return {error: "wrong number of arguments (given #{args.size}, expected #{expected})"}
24
- end
25
-
26
- candidates.reject! { |f| args.size > f.args.size }
27
-
28
- # exclude functions missing required options
29
- candidates.reject! do |func|
30
- # TODO make more generic
31
- func.out? && !options[:out]
32
- end
33
-
34
- # handle out with multiple
35
- # there should only be one match, so safe to modify all
36
- out_func = candidates.find { |f| f.out? }
37
- if out_func && out_func.out_size > 1 && options[:out]
38
- out_args = out_func.args.last(2).map { |a| a[:name] }
39
- out_args.zip(options.delete(:out)).each do |k, v|
40
- options[k.to_sym] = v
41
- end
42
- candidates = [out_func]
43
- end
44
-
45
- # exclude functions where options don't match
46
- options.each do |k, v|
47
- candidates.select! do |func|
48
- func.args.any? { |a| a[:name] == k.to_s }
49
- end
50
- # TODO show all bad keywords at once like Ruby?
51
- return {error: "unknown keyword: #{k}"} if candidates.empty?
52
- end
53
-
54
- final_values = {}
55
-
56
- # check args
57
- candidates.select! do |func|
58
- good = true
59
-
60
- values = args.zip(func.args).map { |a, fa| [fa[:name], a] }.to_h
61
- values.merge!(options.map { |k, v| [k.to_s, v] }.to_h)
62
- func.args.each do |fa|
63
- values[fa[:name]] = fa[:default] if values[fa[:name]].nil?
64
- end
65
-
66
- arg_types = func.args.map { |a| [a[:name], a[:type]] }.to_h
67
-
68
- values.each_key do |k|
69
- v = values[k]
70
- t = arg_types[k].split("(").first
71
-
72
- good =
73
- case t
74
- when "Tensor"
75
- v.is_a?(Tensor)
76
- when "Tensor?"
77
- v.nil? || v.is_a?(Tensor)
78
- when "Tensor[]", "Tensor?[]"
79
- v.is_a?(Array) && v.all? { |v2| v2.is_a?(Tensor) }
80
- when "int"
81
- if k == "reduction"
82
- v.is_a?(String)
83
- else
84
- v.is_a?(Integer)
85
- end
86
- when "int?"
87
- v.is_a?(Integer) || v.nil?
88
- when "float?"
89
- v.is_a?(Numeric) || v.nil?
90
- when "bool?"
91
- v == true || v == false || v.nil?
92
- when "float"
93
- v.is_a?(Numeric)
94
- when /int\[.*\]/
95
- if v.is_a?(Integer)
96
- size = t[4..-2]
97
- raise Error, "Unknown size: #{size}. Please report a bug with #{@name}." unless size =~ /\A\d+\z/
98
- v = [v] * size.to_i
99
- values[k] = v
100
- end
101
- v.is_a?(Array) && v.all? { |v2| v2.is_a?(Integer) }
102
- when "Scalar"
103
- v.is_a?(Numeric)
104
- when "Scalar?"
105
- v.is_a?(Numeric) || v.nil?
106
- when "ScalarType"
107
- false # not supported yet
108
- when "ScalarType?"
109
- v.nil?
110
- when "bool"
111
- v == true || v == false
112
- when "str"
113
- v.is_a?(String)
114
- else
115
- raise Error, "Unknown argument type: #{arg_types[k]}. Please report a bug with #{@name}."
116
- end
117
-
118
- if !good
119
- if candidates.size == 1
120
- k = "input" if k == "self"
121
- return {error: "#{@name}(): argument '#{k}' must be #{t}"}
122
- end
123
- break
124
- end
125
- end
126
-
127
- if good
128
- final_values = values
129
- end
130
-
131
- good
132
- end
133
-
134
- if candidates.size != 1
135
- raise Error, "This should never happen. Please report a bug with #{@name}."
136
- end
137
-
138
- func = candidates.first
139
- args = func.args.map { |a| final_values[a[:name]] }
140
- args << TensorOptions.new.dtype(6) if func.tensor_options
141
- {
142
- name: func.cpp_name,
143
- args: args
144
- }
145
- end
146
- end
147
- end
148
- end