tensor_stream 0.9.1 → 0.9.2

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
- SHA1:
3
- metadata.gz: 9c6649e7cb423246d4886776bf93a8422e3be936
4
- data.tar.gz: c62ffe26455cff685925a5a9a27c49babae6d17e
2
+ SHA256:
3
+ metadata.gz: 9601653d86556739c89b591768e9d54d13d6335dd2f953fff7d91f22636e8c7b
4
+ data.tar.gz: 3db4119c9e752df77cbbf7f8f753050a012b03fd2bde04dd6bbfd01f8903af62
5
5
  SHA512:
6
- metadata.gz: 9dd8519a7c4c34fc0d0c8f4900aa2bec73e6a0dfefe15db4f4c6703188e55080cd2dcce4c8df02993beaa1d6f753b2309fabda623f5ab6a0218c3bd4ce382c0c
7
- data.tar.gz: 2e2f051ada324f4f220db702762a179f1ee20985ecbde51d5c8fc976ad50ff32a315459b97a2366c7cf9a718672b42910466b8e1e81ea388a878035807bae1e0
6
+ metadata.gz: 3ca3eec8ce6cc7e73a1d5e38ccaaf2f65f7a37bb84e0118766de05ec374557847a88f2f14b1e35b896010d7e68a0234589fcf3de95a9417840e2e77ab4a4ea58
7
+ data.tar.gz: 6b109a2ee9b59e286a2d06d1c3166fc5bbda9a888f0c26aa07bc4f994753ce5dbd7cb0aebb433a7446ddef653f3096ae1b60dbc4610e0dc18c9aaca8d3175f2f
data/CHANGELOG.md CHANGED
@@ -4,9 +4,14 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.9.2] - 2018-10-19
8
+ - Add profiling support
9
+ - Make sure sparse ruby arrays are caught
10
+ - Enhancements to support multi-gpu execution
11
+
7
12
  ## [0.9.1] - 2018-10-19
8
13
  - Bug fix release
9
-
14
+
10
15
  ## [0.9.0] - 2018-10-05
11
16
  - Bug fix release for OpenCL gem
12
17
 
@@ -16,7 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
16
21
  - [TRAINING] Added RMSPropOptimizer, AdagradOptimizer
17
22
  - [NEW OP] shape_n, sparse_softmax_cross_entropy_with_logits, split, unstack
18
23
  - Added RNN sample
19
-
24
+
20
25
  ### Fixes
21
26
  - Fixed gradient computation when passing an array of tensors to a function
22
27
  - Added gradients for various other ops
data/lib/tensor_stream.rb CHANGED
@@ -33,6 +33,7 @@ require 'tensor_stream/utils'
33
33
  require 'tensor_stream/train/utils'
34
34
  require 'tensor_stream/images'
35
35
  require 'tensor_stream/trainer'
36
+ require 'tensor_stream/profile/report_tool'
36
37
 
37
38
  # require 'tensor_stream/libraries/layers'
38
39
  require 'tensor_stream/monkey_patches/integer'
@@ -44,4 +45,8 @@ module TensorStream
44
45
  extend TensorStream::Ops
45
46
  extend TensorStream::Debugging
46
47
  extend TensorStream::Utils
48
+
49
+ def self.__version__
50
+ TensorStream::VERSION
51
+ end
47
52
  end
@@ -19,7 +19,10 @@ module TensorStream
19
19
  end
20
20
  end
21
21
 
22
+ ##
22
23
  # Evaluator base class
24
+ #
25
+ # Base class to be used by all tensor_stream evaluators, provides support functions
23
26
  class BaseEvaluator
24
27
  def initialize(session, _device, thread_pool: nil, log_intermediates: false)
25
28
  @session = session
@@ -57,6 +60,7 @@ module TensorStream
57
60
  substrs.each do |q|
58
61
  components = q.split(':')
59
62
  next if components.size.zero?
63
+
60
64
  if components[0] == 'device' # use tensorflow convention
61
65
  device_type = components[1]
62
66
  select_index = components[2].to_i
@@ -79,6 +83,7 @@ module TensorStream
79
83
  evaluator_class = TensorStream::Evaluator.evaluators[components[1]][:class]
80
84
  return nil unless self == evaluator_class
81
85
  return evaluator_class.fetch_device(components[2..components.size]) if evaluator_class.respond_to?(:fetch_device)
86
+
82
87
  return nil
83
88
  end
84
89
  end
@@ -121,14 +126,34 @@ module TensorStream
121
126
  global_eval(tensor, i, execution_context, op_options)
122
127
  end
123
128
 
124
- instance_exec(execution_context, tensor, resolved_inputs, &op[:block])
129
+ start_time = if profile_enabled?
130
+ time = Time.now
131
+ time.to_i * (10**9) + time.nsec
132
+ end
133
+
134
+ instance_exec(execution_context, tensor, resolved_inputs, &op[:block]).tap do
135
+ if profile_enabled?
136
+ time = Time.now
137
+ end_time = time.to_i * (10**9) + time.nsec
138
+ @context[:profile] ||= { step: 0, operations: {} }
139
+ @context[:profile][:step] += 1
140
+ @context[:profile][:operations][tensor.name] = { op: tensor.operation,
141
+ step: @context[:profile][:step],
142
+ eval_time: end_time - start_time,
143
+ tensor: tensor }
144
+ end
145
+ end
125
146
  end
126
147
 
127
148
  protected
128
149
 
150
+ def profile_enabled?
151
+ @context[:_options][:profile_enabled]
152
+ end
153
+
129
154
  ##
130
155
  # called when passing control to another evaluator
131
- def perform_transition(tensor, input, _next_evaluator)
156
+ def perform_transition(tensor, input, _next_evaluator, execution_context)
132
157
  cache_key = "#{tensor.graph.object_id}_#{input.name}:#{object_id}"
133
158
  return @context[:_cache][cache_key] if @context[:_cache].key?(cache_key)
134
159
 
@@ -141,14 +166,20 @@ module TensorStream
141
166
  def global_eval(tensor, input, execution_context, op_options = {})
142
167
  return nil unless input
143
168
  return input unless input.is_a?(Tensor)
169
+ # puts "global eval #{tensor.name}"
144
170
  @context[:_cache][:placement][input.name] = @session.assign_evaluator(input) if @context[:_cache][:placement][input.name].nil?
145
- if object_id != @context[:_cache][:placement][input.name][1].object_id # tensor is on another device or evaluator
146
- perform_transition(tensor, input, @context[:_cache][:placement][input.name][1])
171
+ if !on_same_device?(input) # tensor is on another device or evaluator
172
+ # puts "transition #{object_id} -> #{@context[:_cache][:placement][input.name][1].object_id}"
173
+ perform_transition(tensor, input, @context[:_cache][:placement][input.name][1], execution_context)
147
174
  else
148
175
  prepare_input(input, execution_context, op_options)
149
176
  end
150
177
  end
151
178
 
179
+ def on_same_device?(tensor)
180
+ object_id == @context[:_cache][:placement][tensor.name][1].object_id
181
+ end
182
+
152
183
  def get_broadcast_gradient_args(input_a, input_b)
153
184
  return [[], []] if input_a == input_b
154
185
 
@@ -186,7 +186,6 @@ module TensorStream
186
186
  end
187
187
  reduced_val
188
188
  end
189
-
190
189
  reduction(context, tensor, func)
191
190
  end
192
191
 
@@ -282,15 +282,17 @@ module TensorStream
282
282
 
283
283
  def eval_operation(tensor, child_context)
284
284
  return @context[tensor.name] if @context.key?(tensor.name)
285
- invoke(tensor, child_context).tap do |result|
286
285
 
286
+ # puts "ruby eval #{object_id}: #{tensor.name}"
287
+ invoke(tensor, child_context).tap do |result|
288
+ # puts "result done ruby #{object_id}: #{tensor.name}"
287
289
  # assertions to make sure inferred shapes == actual evaluated shapes
288
290
  if tensor.shape.known? && (result.is_a?(Array) || result.is_a?(Float) || result.is_a?(Integer))
289
291
  if shape_eval(result) != tensor.shape.shape
290
292
  raise "assert error #{tensor.name} #{shape_eval(result)} != #{tensor.shape.shape}"
291
293
  end
292
294
  end
293
-
295
+
294
296
  if tensor.breakpoint
295
297
  a = resolve_placeholder(tensor.inputs[0], child_context) if tensor.inputs && tensor.inputs[0]
296
298
  b = resolve_placeholder(tensor.inputs[1], child_context) if tensor.inputs && tensor.inputs[1]
@@ -0,0 +1,18 @@
1
+ module TensorStream
2
+ ##
3
+ # Utiliity functions for creating performance reports
4
+ class ReportTool
5
+ def self.profile_for(session, order_by: :slowest)
6
+ context = session.last_session_context
7
+ eval_times = context[:profile][:operations].map do |name, profile|
8
+ [name, profile[:eval_time], profile[:tensor].source]
9
+ end
10
+
11
+ if order_by == :slowest
12
+ eval_times.sort_by { |a, b| b[1] <=> a[1] }
13
+ else
14
+ eval_times.sort_by { |a, b| a[1] <=> b[1] }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -6,13 +6,13 @@ module TensorStream
6
6
  attr_reader :last_session_context, :closed, :target, :session_cache
7
7
  attr_accessor :randomizer
8
8
 
9
- def initialize(evaluator = nil, thread_pool_class: Concurrent::ImmediateExecutor, log_device_placement: false, evaluator_options: {})
9
+ def initialize(evaluator = nil, thread_pool_class: Concurrent::ImmediateExecutor, log_device_placement: false, profile_enabled: false, evaluator_options: {})
10
10
  @thread_pool = thread_pool_class.new
11
11
  @closed = false
12
12
  @session_cache = {}
13
13
  @randomizer = {}
14
14
  @log_device_placement = log_device_placement
15
- @evaluator_options = evaluator_options
15
+ @evaluator_options = evaluator_options.merge(profile_enabled: profile_enabled)
16
16
  get_evaluator_classes(evaluator)
17
17
  @evaluators = {}
18
18
  end
@@ -45,8 +45,13 @@ module TensorStream
45
45
  else
46
46
  {}
47
47
  end
48
+
49
+ @evaluator_options[:thread_pool] = @thread_pool
50
+ @evaluator_options[:log_intermediates] = options[:log_intermediates]
51
+
48
52
  context = {
49
- _cache: @session_cache
53
+ _cache: @session_cache,
54
+ _options: options.merge(@evaluator_options)
50
55
  }
51
56
 
52
57
  # scan for placeholders and assign value
@@ -56,8 +61,6 @@ module TensorStream
56
61
  end
57
62
  end
58
63
 
59
- @evaluator_options[:thread_pool] = @thread_pool
60
- @evaluator_options[:log_intermediates] = options[:log_intermediates]
61
64
 
62
65
  args.each { |t| prepare_evaluators(t, context) }
63
66
  @last_session_context = context
@@ -69,6 +72,7 @@ module TensorStream
69
72
  end
70
73
  result = args.collect do |e|
71
74
  next e.value if e.is_a?(Tensor) && e.is_const && e.value
75
+
72
76
  value = delegate_to_evaluator(e, context, {})
73
77
  recursive_eval(value)
74
78
  end
@@ -129,8 +129,18 @@ module TensorStream
129
129
  Thread.current[:tensor_stream_variable_scope].map(&:name).compact.reject(&:empty?).join('/')
130
130
  end
131
131
 
132
- def session(evaluator = nil, thread_pool_class: Concurrent::ImmediateExecutor, log_device_placement: false)
133
- session = TensorStream::Session.new(evaluator, thread_pool_class: thread_pool_class, log_device_placement: log_device_placement)
132
+ ##
133
+ # Creates a session context where operations can be executed
134
+ #
135
+ # Args:
136
+ # evaluator: Specific evaluator to use, otherwise the best evaluator will automatically be determined
137
+ #
138
+ # Options:
139
+ # thread_pool_class: Class to use to manage thread pooling
140
+ # log_device_placement: Show assigned device/evalutor for each tensor op
141
+ # profile_enabled: Log performance metrics for each operation
142
+ def session(evaluator = nil, thread_pool_class: Concurrent::ImmediateExecutor, log_device_placement: false, profile_enabled: false)
143
+ session = TensorStream::Session.new(evaluator, thread_pool_class: thread_pool_class, log_device_placement: log_device_placement, profile_enabled: profile_enabled)
134
144
  yield session if block_given?
135
145
 
136
146
  session
@@ -151,6 +161,7 @@ module TensorStream
151
161
 
152
162
  def constant(value, dtype: nil, shape: nil, internal: false, name: 'Const')
153
163
  shared_options = { const: true, value: value, name: name, internal: internal }
164
+
154
165
  if value.is_a?(Float)
155
166
  TensorStream::Tensor.new(dtype || :float32, 0, shape || [], shared_options)
156
167
  elsif value.is_a?(Integer)
@@ -162,6 +173,7 @@ module TensorStream
162
173
  elsif value.is_a?(Array)
163
174
  dimension = shape || shape_eval(value)
164
175
  rank = dimension.size
176
+ TensorStream.check_if_dense(value)
165
177
 
166
178
  cur_dtype = dtype || Tensor.detect_type(value.flatten.last)
167
179
  value = Tensor.cast_dtype(value, cur_dtype) unless dtype.nil?
@@ -224,18 +236,39 @@ module TensorStream
224
236
  TensorStream.get_default_graph.control_dependencies(control_inputs, &block)
225
237
  end
226
238
 
227
- def convert_to_tensor(value, dtype: nil, name: nil, preferred_dtype: nil)
239
+ def convert_to_tensor(value, dtype: nil, name: nil)
228
240
  return value if value.is_a?(Tensor)
229
241
  return convert_to_tensor(value.call) if value.is_a?(Proc)
242
+
230
243
  if value.is_a?(Array) && value[0].is_a?(Tensor)
231
244
  return TensorStream.stack(value) if value.size > 1
232
245
 
233
246
  return TensorStream.expand_dims(value[0], 0)
234
247
  end
235
248
 
249
+ check_if_dense(value)
236
250
  i_cons(value, dtype: dtype || Tensor.detect_type(value), name: name)
237
251
  end
238
252
 
253
+ ##
254
+ # Check to make sure passed array is dense
255
+ #
256
+ def check_if_dense(value, expected_shape = nil)
257
+ return unless value.is_a?(Array)
258
+ return if value.empty?
259
+
260
+ expected_shape ||= shape_eval(value)
261
+
262
+ s = expected_shape.shift
263
+ raise TensorStream::ValueError, "Argument must be a dense tensor: #{value}, expected size #{s} got #{value.size}" if value.size != s
264
+
265
+ return if expected_shape.empty?
266
+
267
+ value.each do |item|
268
+ check_if_dense(item, expected_shape.dup)
269
+ end
270
+ end
271
+
239
272
  def check_allowed_types(input, types)
240
273
  return input unless input.is_a?(Tensor)
241
274
  return input if input.data_type.nil?
@@ -1,5 +1,5 @@
1
1
  module TensorStream
2
- VERSION = '0.9.1'.freeze
2
+ VERSION = '0.9.2'.freeze
3
3
 
4
4
  def self.version
5
5
  VERSION
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tensor_stream
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joseph Emmanuel Dayo
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-10-19 00:00:00.000000000 Z
11
+ date: 2018-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -285,6 +285,7 @@ files:
285
285
  - lib/tensor_stream/operation.rb
286
286
  - lib/tensor_stream/ops.rb
287
287
  - lib/tensor_stream/placeholder.rb
288
+ - lib/tensor_stream/profile/report_tool.rb
288
289
  - lib/tensor_stream/session.rb
289
290
  - lib/tensor_stream/tensor.rb
290
291
  - lib/tensor_stream/tensor_shape.rb
@@ -336,7 +337,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
336
337
  version: '0'
337
338
  requirements: []
338
339
  rubyforge_project:
339
- rubygems_version: 2.6.10
340
+ rubygems_version: 2.7.7
340
341
  signing_key:
341
342
  specification_version: 4
342
343
  summary: A Pure ruby tensorflow implementation