aboutsummaryrefslogtreecommitdiffhomepage
path: root/tensorflow
diff options
context:
space:
mode:
authorGravatar avijit-nervana <avijit.chakraborty@intel.com>2018-07-31 23:04:56 -0700
committerGravatar avijit-nervana <avijit.chakraborty@intel.com>2018-07-31 23:04:56 -0700
commit2f8b328d2571625161b3c0da0a9b25b907a0927e (patch)
treed264a0d5dbfcd774cccd0f2c23d7616897c1b894 /tensorflow
parent2f3e97cf0ee50ee0e55ab1a3795cc82537426e8c (diff)
parent7ca6ee15555db77c09861fc7e84e5181001da07d (diff)
Merge remote-tracking branch 'upstream/master' and changes
based on PR review comments.
Diffstat (limited to 'tensorflow')
-rw-r--r--tensorflow/BUILD16
-rw-r--r--tensorflow/c/c_api.cc41
-rw-r--r--tensorflow/c/c_api.h39
-rw-r--r--tensorflow/c/c_api_experimental.cc12
-rw-r--r--tensorflow/c/c_api_experimental.h6
-rw-r--r--tensorflow/c/c_api_function.cc4
-rw-r--r--tensorflow/c/c_api_function_test.cc1
-rw-r--r--tensorflow/c/c_api_test.cc84
-rw-r--r--tensorflow/c/eager/c_api.cc4
-rw-r--r--tensorflow/c/eager/c_api.h3
-rw-r--r--tensorflow/c/eager/c_api_test.cc54
-rw-r--r--tensorflow/cc/gradients/math_grad_test.cc6
-rw-r--r--tensorflow/cc/saved_model/loader.cc95
-rw-r--r--tensorflow/compiler/aot/BUILD1
-rw-r--r--tensorflow/compiler/aot/compile.cc1
-rw-r--r--tensorflow/compiler/jit/BUILD1
-rw-r--r--tensorflow/compiler/jit/kernels/xla_launch_op.cc4
-rw-r--r--tensorflow/compiler/jit/xla_compilation_cache.cc4
-rw-r--r--tensorflow/compiler/jit/xla_device.h7
-rw-r--r--tensorflow/compiler/tests/BUILD2
-rw-r--r--tensorflow/compiler/tests/image_ops_test.py136
-rw-r--r--tensorflow/compiler/tf2xla/BUILD12
-rw-r--r--tensorflow/compiler/tf2xla/dump_graph.cc53
-rw-r--r--tensorflow/compiler/tf2xla/functionalize_control_flow.cc37
-rw-r--r--tensorflow/compiler/tf2xla/functionalize_control_flow_test.cc5
-rw-r--r--tensorflow/compiler/tf2xla/graph_compiler.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/BUILD11
-rw-r--r--tensorflow/compiler/tf2xla/kernels/aggregate_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/batch_norm_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/batchtospace_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/bias_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/binary_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/bucketize_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/cast_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/categorical_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/clip_by_value_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/concat_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/const_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/conv_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/cross_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/cwise_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/cwise_ops.h2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/depthtospace_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/diag_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/dynamic_slice_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/dynamic_stitch_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/elu_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/extract_image_patches_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/fake_quantize_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/fft_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/fill_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/gather_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/gather_op_helpers.h2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/if_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/image_ops.cc152
-rw-r--r--tensorflow/compiler/tf2xla/kernels/image_resize_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/l2loss_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/listdiff_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/lrn_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/matmul_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/matrix_band_part_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/matrix_set_diag_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/mirror_pad_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/pack_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/pad_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/pooling_ops.cc3
-rw-r--r--tensorflow/compiler/tf2xla/kernels/quantize_and_dequantize_op.cc3
-rw-r--r--tensorflow/compiler/tf2xla/kernels/random_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/reduce_window_op.cc3
-rw-r--r--tensorflow/compiler/tf2xla/kernels/reduction_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/reduction_ops.h2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/reduction_ops_common.cc3
-rw-r--r--tensorflow/compiler/tf2xla/kernels/relu_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/reshape_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/retval_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/reverse_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/reverse_sequence_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/scan_ops.cc3
-rw-r--r--tensorflow/compiler/tf2xla/kernels/scatter_nd_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/segment_reduction_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/select_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/sendrecv_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/shape_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/slice_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/softmax_op.cc3
-rw-r--r--tensorflow/compiler/tf2xla/kernels/sort_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/spacetobatch_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/spacetodepth_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/split_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/stateless_random_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/strided_slice_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/tensor_array_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/tile_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/topk_op.cc29
-rw-r--r--tensorflow/compiler/tf2xla/kernels/training_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/transpose_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/unary_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/unpack_op.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/variable_ops.cc2
-rw-r--r--tensorflow/compiler/tf2xla/kernels/while_op.cc3
-rw-r--r--tensorflow/compiler/tf2xla/legacy_flags/backend_registration_flags.cc63
-rw-r--r--tensorflow/compiler/tf2xla/legacy_flags/backend_registration_flags.h49
-rw-r--r--tensorflow/compiler/tf2xla/lib/BUILD32
-rw-r--r--tensorflow/compiler/tf2xla/lib/batch_dot.cc2
-rw-r--r--tensorflow/compiler/tf2xla/lib/batch_dot.h4
-rw-r--r--tensorflow/compiler/tf2xla/lib/cholesky.cc2
-rw-r--r--tensorflow/compiler/tf2xla/lib/cholesky.h4
-rw-r--r--tensorflow/compiler/tf2xla/lib/qr.cc2
-rw-r--r--tensorflow/compiler/tf2xla/lib/qr.h2
-rw-r--r--tensorflow/compiler/tf2xla/lib/random.cc2
-rw-r--r--tensorflow/compiler/tf2xla/lib/random.h2
-rw-r--r--tensorflow/compiler/tf2xla/lib/scatter.cc2
-rw-r--r--tensorflow/compiler/tf2xla/lib/scatter.h4
-rw-r--r--tensorflow/compiler/tf2xla/lib/triangular_solve.cc3
-rw-r--r--tensorflow/compiler/tf2xla/lib/triangular_solve.h4
-rw-r--r--tensorflow/compiler/tf2xla/lib/triangular_solve_test.cc2
-rw-r--r--tensorflow/compiler/tf2xla/lib/util.cc2
-rw-r--r--tensorflow/compiler/tf2xla/lib/util.h4
-rw-r--r--tensorflow/compiler/tf2xla/lib/while_loop.cc2
-rw-r--r--tensorflow/compiler/tf2xla/lib/while_loop.h4
-rw-r--r--tensorflow/compiler/tf2xla/tf2xla.cc1
-rw-r--r--tensorflow/compiler/tf2xla/tf2xla.h2
-rw-r--r--tensorflow/compiler/tf2xla/tf2xla_test.cc1
-rw-r--r--tensorflow/compiler/tf2xla/xla_compilation_device.cc2
-rw-r--r--tensorflow/compiler/tf2xla/xla_compilation_device.h2
-rw-r--r--tensorflow/compiler/tf2xla/xla_compiler.cc16
-rw-r--r--tensorflow/compiler/tf2xla/xla_compiler.h7
-rw-r--r--tensorflow/compiler/tf2xla/xla_compiler_test.cc13
-rw-r--r--tensorflow/compiler/tf2xla/xla_context.cc3
-rw-r--r--tensorflow/compiler/tf2xla/xla_context.h4
-rw-r--r--tensorflow/compiler/tf2xla/xla_gpu_backend.cc15
-rw-r--r--tensorflow/compiler/tf2xla/xla_helpers.cc3
-rw-r--r--tensorflow/compiler/tf2xla/xla_helpers.h2
-rw-r--r--tensorflow/compiler/tf2xla/xla_jit_compiled_cpu_function.cc1
-rw-r--r--tensorflow/compiler/tf2xla/xla_op_kernel.cc3
-rw-r--r--tensorflow/compiler/tf2xla/xla_op_kernel.h3
-rw-r--r--tensorflow/compiler/tf2xla/xla_resource.cc2
-rw-r--r--tensorflow/compiler/tf2xla/xla_resource.h2
-rw-r--r--tensorflow/compiler/xla/BUILD2
-rw-r--r--tensorflow/compiler/xla/client/BUILD64
-rw-r--r--tensorflow/compiler/xla/client/client.cc1
-rw-r--r--tensorflow/compiler/xla/client/client.h2
-rw-r--r--tensorflow/compiler/xla/client/compile_only_client.h2
-rw-r--r--tensorflow/compiler/xla/client/lib/BUILD53
-rw-r--r--tensorflow/compiler/xla/client/lib/arithmetic.cc4
-rw-r--r--tensorflow/compiler/xla/client/lib/arithmetic.h4
-rw-r--r--tensorflow/compiler/xla/client/lib/constants.h2
-rw-r--r--tensorflow/compiler/xla/client/lib/constants_test.cc2
-rw-r--r--tensorflow/compiler/xla/client/lib/math.h2
-rw-r--r--tensorflow/compiler/xla/client/lib/math_test.cc2
-rw-r--r--tensorflow/compiler/xla/client/lib/numeric.h2
-rw-r--r--tensorflow/compiler/xla/client/lib/numeric_test.cc2
-rw-r--r--tensorflow/compiler/xla/client/lib/prng.cc2
-rw-r--r--tensorflow/compiler/xla/client/lib/prng.h2
-rw-r--r--tensorflow/compiler/xla/client/lib/sorting.cc46
-rw-r--r--tensorflow/compiler/xla/client/lib/sorting.h31
-rw-r--r--tensorflow/compiler/xla/client/lib/sorting_test.cc60
-rw-r--r--tensorflow/compiler/xla/client/lib/testing.cc2
-rw-r--r--tensorflow/compiler/xla/client/lib/testing.h2
-rw-r--r--tensorflow/compiler/xla/client/local_client.cc8
-rw-r--r--tensorflow/compiler/xla/client/local_client.h2
-rw-r--r--tensorflow/compiler/xla/client/xla_builder.cc (renamed from tensorflow/compiler/xla/client/xla_client/xla_builder.cc)3
-rw-r--r--tensorflow/compiler/xla/client/xla_builder.h2241
-rw-r--r--tensorflow/compiler/xla/client/xla_builder_test.cc (renamed from tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc)3
-rw-r--r--tensorflow/compiler/xla/client/xla_client/BUILD48
-rw-r--r--tensorflow/compiler/xla/client/xla_client/xla_builder.h2222
-rw-r--r--tensorflow/compiler/xla/client/xla_computation.cc (renamed from tensorflow/compiler/xla/client/xla_client/xla_computation.cc)2
-rw-r--r--tensorflow/compiler/xla/client/xla_computation.h (renamed from tensorflow/compiler/xla/client/xla_client/xla_computation.h)6
-rw-r--r--tensorflow/compiler/xla/experimental/xla_sharding/xla_sharding.py4
-rw-r--r--tensorflow/compiler/xla/python/BUILD4
-rw-r--r--tensorflow/compiler/xla/python/local_computation_builder.cc6
-rw-r--r--tensorflow/compiler/xla/python/local_computation_builder.h8
-rw-r--r--tensorflow/compiler/xla/python/local_computation_builder.i4
-rw-r--r--tensorflow/compiler/xla/python/numpy_bridge.cc11
-rw-r--r--tensorflow/compiler/xla/python/xla_client.py4
-rw-r--r--tensorflow/compiler/xla/python_api/types.py28
-rw-r--r--tensorflow/compiler/xla/python_api/xla_literal.py12
-rw-r--r--tensorflow/compiler/xla/python_api/xla_shape.py4
-rw-r--r--tensorflow/compiler/xla/reference_util.cc2
-rw-r--r--tensorflow/compiler/xla/rpc/BUILD2
-rw-r--r--tensorflow/compiler/xla/rpc/grpc_client_test.cc2
-rw-r--r--tensorflow/compiler/xla/service/BUILD34
-rw-r--r--tensorflow/compiler/xla/service/algebraic_simplifier.cc40
-rw-r--r--tensorflow/compiler/xla/service/algebraic_simplifier_test.cc42
-rw-r--r--tensorflow/compiler/xla/service/backend.cc17
-rw-r--r--tensorflow/compiler/xla/service/backend.h14
-rw-r--r--tensorflow/compiler/xla/service/bfloat16_conversion_folding_test.cc3
-rw-r--r--tensorflow/compiler/xla/service/bfloat16_normalization.cc65
-rw-r--r--tensorflow/compiler/xla/service/bfloat16_normalization_test.cc30
-rw-r--r--tensorflow/compiler/xla/service/bfloat16_propagation.cc14
-rw-r--r--tensorflow/compiler/xla/service/bfloat16_propagation_test.cc69
-rw-r--r--tensorflow/compiler/xla/service/buffer_assignment.cc25
-rw-r--r--tensorflow/compiler/xla/service/buffer_assignment.h4
-rw-r--r--tensorflow/compiler/xla/service/buffer_assignment_test.cc68
-rw-r--r--tensorflow/compiler/xla/service/computation_placer.cc17
-rw-r--r--tensorflow/compiler/xla/service/computation_placer.h2
-rw-r--r--tensorflow/compiler/xla/service/cpu/BUILD6
-rw-r--r--tensorflow/compiler/xla/service/cpu/cpu_compiler.cc16
-rw-r--r--tensorflow/compiler/xla/service/cpu/cpu_executable.cc5
-rw-r--r--tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc38
-rw-r--r--tensorflow/compiler/xla/service/cpu/dot_op_emitter.h11
-rw-r--r--tensorflow/compiler/xla/service/cpu/ir_emitter.cc61
-rw-r--r--tensorflow/compiler/xla/service/cpu/ir_emitter.h7
-rw-r--r--tensorflow/compiler/xla/service/cpu/llvm_ir_runtime.cc39
-rw-r--r--tensorflow/compiler/xla/service/cpu/sample_harness.cc4
-rw-r--r--tensorflow/compiler/xla/service/cpu/tests/BUILD4
-rw-r--r--tensorflow/compiler/xla/service/cpu/tests/cpu_infeed_test.cc4
-rw-r--r--tensorflow/compiler/xla/service/elemental_ir_emitter.cc406
-rw-r--r--tensorflow/compiler/xla/service/elemental_ir_emitter.h11
-rw-r--r--tensorflow/compiler/xla/service/execution_tracker.cc9
-rw-r--r--tensorflow/compiler/xla/service/execution_tracker.h8
-rw-r--r--tensorflow/compiler/xla/service/gpu/BUILD45
-rw-r--r--tensorflow/compiler/xla/service/gpu/buffer_allocations.cc23
-rw-r--r--tensorflow/compiler/xla/service/gpu/buffer_allocations.h6
-rw-r--r--tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.cc11
-rw-r--r--tensorflow/compiler/xla/service/gpu/gemm_thunk.cc25
-rw-r--r--tensorflow/compiler/xla/service/gpu/gpu_constants.cc2
-rw-r--r--tensorflow/compiler/xla/service/gpu/gpu_constants.h3
-rw-r--r--tensorflow/compiler/xla/service/gpu/gpu_copy_insertion.cc79
-rw-r--r--tensorflow/compiler/xla/service/gpu/gpu_executable.cc62
-rw-r--r--tensorflow/compiler/xla/service/gpu/gpu_executable.h21
-rw-r--r--tensorflow/compiler/xla/service/gpu/gpu_layout_assignment.cc58
-rw-r--r--tensorflow/compiler/xla/service/gpu/hlo_execution_profiler.cc11
-rw-r--r--tensorflow/compiler/xla/service/gpu/hlo_execution_profiler.h12
-rw-r--r--tensorflow/compiler/xla/service/gpu/hlo_to_ir_bindings.cc25
-rw-r--r--tensorflow/compiler/xla/service/gpu/instruction_fusion.cc106
-rw-r--r--tensorflow/compiler/xla/service/gpu/instruction_fusion.h17
-rw-r--r--tensorflow/compiler/xla/service/gpu/instruction_fusion_test.cc4
-rw-r--r--tensorflow/compiler/xla/service/gpu/ir_emission_utils.cc5
-rw-r--r--tensorflow/compiler/xla/service/gpu/ir_emission_utils.h6
-rw-r--r--tensorflow/compiler/xla/service/gpu/ir_emitter.cc30
-rw-r--r--tensorflow/compiler/xla/service/gpu/ir_emitter.h1
-rw-r--r--tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc214
-rw-r--r--tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h3
-rw-r--r--tensorflow/compiler/xla/service/gpu/multi_output_fusion.cc15
-rw-r--r--tensorflow/compiler/xla/service/gpu/multi_output_fusion_test.cc54
-rw-r--r--tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc23
-rw-r--r--tensorflow/compiler/xla/service/gpu/pad_for_tensor_cores.cc233
-rw-r--r--tensorflow/compiler/xla/service/gpu/pad_for_tensor_cores.h45
-rw-r--r--tensorflow/compiler/xla/service/gpu/pad_for_tensor_cores_test.cc164
-rw-r--r--tensorflow/compiler/xla/service/gpu/stream_assignment.cc70
-rw-r--r--tensorflow/compiler/xla/service/gpu/stream_executor_util.cc15
-rw-r--r--tensorflow/compiler/xla/service/gpu/stream_executor_util.h15
-rw-r--r--tensorflow/compiler/xla/service/gpu/tests/BUILD2
-rw-r--r--tensorflow/compiler/xla/service/gpu/tests/infeed_test.cc2
-rw-r--r--tensorflow/compiler/xla/service/gpu/while_thunk.cc3
-rw-r--r--tensorflow/compiler/xla/service/hlo_cost_analysis_test.cc4
-rw-r--r--tensorflow/compiler/xla/service/hlo_dataflow_analysis.cc54
-rw-r--r--tensorflow/compiler/xla/service/hlo_dataflow_analysis_test.cc42
-rw-r--r--tensorflow/compiler/xla/service/hlo_evaluator_test.cc2
-rw-r--r--tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h8
-rw-r--r--tensorflow/compiler/xla/service/hlo_instruction.h3
-rw-r--r--tensorflow/compiler/xla/service/hlo_instructions.h3
-rw-r--r--tensorflow/compiler/xla/service/hlo_parser.cc11
-rw-r--r--tensorflow/compiler/xla/service/hlo_parser_test.cc11
-rw-r--r--tensorflow/compiler/xla/service/hlo_verifier.cc2
-rw-r--r--tensorflow/compiler/xla/service/llvm_ir/BUILD19
-rw-r--r--tensorflow/compiler/xla/service/llvm_ir/alias_analysis_test.cc2
-rw-r--r--tensorflow/compiler/xla/service/llvm_ir/buffer_assignment_util.cc59
-rw-r--r--tensorflow/compiler/xla/service/llvm_ir/buffer_assignment_util.h34
-rw-r--r--tensorflow/compiler/xla/service/llvm_ir/dynamic_update_slice_util.cc4
-rw-r--r--tensorflow/compiler/xla/service/llvm_ir/ir_array.cc1
-rw-r--r--tensorflow/compiler/xla/service/llvm_ir/llvm_util.cc51
-rw-r--r--tensorflow/compiler/xla/service/llvm_ir/llvm_util.h21
-rw-r--r--tensorflow/compiler/xla/service/llvm_ir/math_ops.cc59
-rw-r--r--tensorflow/compiler/xla/service/llvm_ir/math_ops.h (renamed from tensorflow/compiler/xla/service/pool_test.cc)32
-rw-r--r--tensorflow/compiler/xla/service/llvm_ir/sort_util.cc46
-rw-r--r--tensorflow/compiler/xla/service/llvm_ir/sort_util.h2
-rw-r--r--tensorflow/compiler/xla/service/local_service.cc1
-rw-r--r--tensorflow/compiler/xla/service/local_service.h2
-rw-r--r--tensorflow/compiler/xla/service/pool.h84
-rw-r--r--tensorflow/compiler/xla/service/service.cc11
-rw-r--r--tensorflow/compiler/xla/service/service_executable_run_options.h7
-rw-r--r--tensorflow/compiler/xla/service/stream_pool.cc56
-rw-r--r--tensorflow/compiler/xla/service/stream_pool.h64
-rw-r--r--tensorflow/compiler/xla/service/stream_pool_test.cc136
-rw-r--r--tensorflow/compiler/xla/service/transpose_folding_test.cc2
-rw-r--r--tensorflow/compiler/xla/service/tuple_points_to_analysis.cc16
-rw-r--r--tensorflow/compiler/xla/service/tuple_points_to_analysis_test.cc42
-rw-r--r--tensorflow/compiler/xla/tests/BUILD274
-rw-r--r--tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/axpy_simple_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/bad_rng_shape_validation_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/batch_normalization_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/bfloat16_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/binop_scaling_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/bitcast_convert_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/broadcast_simple_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/call_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/check_execution_arity_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/client_library_test_base.cc2
-rw-r--r--tensorflow/compiler/xla/tests/client_library_test_base.h4
-rw-r--r--tensorflow/compiler/xla/tests/client_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/compilation_cache_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/compute_constant_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/concat_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/conditional_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/constants_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/convert_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/convolution_dimension_numbers_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/convolution_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/convolution_variants_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/copy_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/custom_call_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/deallocation_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/deconstruct_tuple_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/deep_graph_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/dot_operation_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/dynamic_ops_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/execution_profile_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/exhaustive_f32_elementwise_op_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/floor_ceil_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/fmax_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/fusion_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/gather_operation_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/half_test.cc5
-rw-r--r--tensorflow/compiler/xla/tests/hlo_metadata_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/local_client_allocation_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/local_client_aot_test_helper.cc7
-rw-r--r--tensorflow/compiler/xla/tests/local_client_execute_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/local_client_test_base.cc1
-rw-r--r--tensorflow/compiler/xla/tests/local_client_test_base.h2
-rw-r--r--tensorflow/compiler/xla/tests/log_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/map_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/matrix_ops_simple_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/multidimensional_slice_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/outfeed_in_nested_computation_test.cc169
-rw-r--r--tensorflow/compiler/xla/tests/pad_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/params_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/pred_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/prng_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/query_inferred_shape_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/reduce_precision_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/reduce_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/reduce_window_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/replay_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/reshape_motion_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/reshape_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/reverse_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/scalar_computations_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/select_and_scatter_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/select_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/slice_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/test_utils_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/transfer_manager_test.cc3
-rw-r--r--tensorflow/compiler/xla/tests/transpose_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/tuple_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/unary_op_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/vector_ops_reduce_test.cc2
-rw-r--r--tensorflow/compiler/xla/tests/vector_ops_simple_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/while_test.cc4
-rw-r--r--tensorflow/compiler/xla/tests/xla_hlo_profile_test.cc7
-rw-r--r--tensorflow/compiler/xla/tools/BUILD5
-rw-r--r--tensorflow/compiler/xla/tools/dumped_computation_to_graphviz.cc1
-rw-r--r--tensorflow/compiler/xla/tools/dumped_computation_to_operation_list.cc1
-rw-r--r--tensorflow/compiler/xla/tools/dumped_computation_to_text.cc1
-rw-r--r--tensorflow/compiler/xla/tools/dumped_computation_to_tf_graphdef.cc1
-rw-r--r--tensorflow/compiler/xla/tools/replay_computation.cc1
-rw-r--r--tensorflow/contrib/BUILD6
-rw-r--r--tensorflow/contrib/autograph/converters/asserts_test.py2
-rw-r--r--tensorflow/contrib/autograph/converters/break_statements.py6
-rw-r--r--tensorflow/contrib/autograph/converters/break_statements_test.py38
-rw-r--r--tensorflow/contrib/autograph/converters/continue_statements.py4
-rw-r--r--tensorflow/contrib/autograph/converters/continue_statements_test.py32
-rw-r--r--tensorflow/contrib/autograph/converters/directives_test.py6
-rw-r--r--tensorflow/contrib/autograph/converters/error_handlers.py3
-rw-r--r--tensorflow/contrib/autograph/converters/error_handlers_test.py6
-rw-r--r--tensorflow/contrib/autograph/converters/lists_test.py4
-rw-r--r--tensorflow/contrib/autograph/converters/side_effect_guards_test.py12
-rw-r--r--tensorflow/contrib/autograph/converters/slices.py6
-rw-r--r--tensorflow/contrib/autograph/converters/slices_test.py6
-rw-r--r--tensorflow/contrib/autograph/core/converter_testing.py4
-rw-r--r--tensorflow/contrib/autograph/core/errors.py218
-rw-r--r--tensorflow/contrib/autograph/core/errors_test.py108
-rw-r--r--tensorflow/contrib/autograph/examples/integration_tests/BUILD15
-rw-r--r--tensorflow/contrib/autograph/examples/integration_tests/errors_test.py162
-rw-r--r--tensorflow/contrib/autograph/examples/notebooks/dev_summit_2018_demo.ipynb2
-rw-r--r--tensorflow/contrib/autograph/examples/notebooks/graph_vs_ag_vs_eager_sum_speed_test.ipynb519
-rw-r--r--tensorflow/contrib/autograph/impl/api.py31
-rw-r--r--tensorflow/contrib/autograph/impl/api_test.py14
-rw-r--r--tensorflow/contrib/autograph/impl/conversion.py50
-rw-r--r--tensorflow/contrib/autograph/impl/conversion_test.py33
-rw-r--r--tensorflow/contrib/autograph/pyct/BUILD10
-rw-r--r--tensorflow/contrib/autograph/pyct/ast_util.py87
-rw-r--r--tensorflow/contrib/autograph/pyct/ast_util_test.py62
-rw-r--r--tensorflow/contrib/autograph/pyct/cfg.py6
-rw-r--r--tensorflow/contrib/autograph/pyct/common_transformers/BUILD1
-rw-r--r--tensorflow/contrib/autograph/pyct/common_transformers/anf.py381
-rw-r--r--tensorflow/contrib/autograph/pyct/common_transformers/anf_test.py358
-rw-r--r--tensorflow/contrib/autograph/pyct/compiler.py127
-rw-r--r--tensorflow/contrib/autograph/pyct/compiler_test.py2
-rw-r--r--tensorflow/contrib/autograph/pyct/origin_info.py137
-rw-r--r--tensorflow/contrib/autograph/pyct/origin_info_test.py101
-rw-r--r--tensorflow/contrib/autograph/pyct/parser.py1
-rw-r--r--tensorflow/contrib/autograph/pyct/templates.py4
-rw-r--r--tensorflow/contrib/autograph/pyct/templates_test.py13
-rw-r--r--tensorflow/contrib/autograph/pyct/testing/BUILD43
-rw-r--r--tensorflow/contrib/autograph/pyct/testing/codegen.py234
-rw-r--r--tensorflow/contrib/autograph/pyct/testing/codegen_test.py40
-rw-r--r--tensorflow/contrib/autograph/pyct/transformer.py17
-rw-r--r--tensorflow/contrib/autograph/utils/builtins.py10
-rw-r--r--tensorflow/contrib/boosted_trees/estimator_batch/BUILD1
-rw-r--r--tensorflow/contrib/boosted_trees/estimator_batch/dnn_tree_combined_estimator.py265
-rw-r--r--tensorflow/contrib/boosted_trees/estimator_batch/dnn_tree_combined_estimator_test.py70
-rw-r--r--tensorflow/contrib/boosted_trees/estimator_batch/estimator.py129
-rw-r--r--tensorflow/contrib/boosted_trees/estimator_batch/estimator_test.py30
-rw-r--r--tensorflow/contrib/boosted_trees/estimator_batch/model.py80
-rw-r--r--tensorflow/contrib/boosted_trees/kernels/quantile_ops.cc5
-rw-r--r--tensorflow/contrib/boosted_trees/kernels/training_ops.cc17
-rw-r--r--tensorflow/contrib/boosted_trees/lib/quantiles/weighted_quantiles_stream.h5
-rw-r--r--tensorflow/contrib/boosted_trees/ops/training_ops.cc3
-rw-r--r--tensorflow/contrib/boosted_trees/python/kernel_tests/training_ops_test.py84
-rw-r--r--tensorflow/contrib/boosted_trees/python/training/functions/gbdt_batch.py14
-rw-r--r--tensorflow/contrib/cloud/kernels/bigquery_table_accessor.cc36
-rw-r--r--tensorflow/contrib/cloud/kernels/bigquery_table_accessor.h13
-rw-r--r--tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver.py6
-rw-r--r--tensorflow/contrib/cmake/CMakeLists.txt1
-rw-r--r--tensorflow/contrib/copy_graph/python/util/copy_elements.py4
-rw-r--r--tensorflow/contrib/data/kernels/prefetching_kernels.cc9
-rw-r--r--tensorflow/contrib/data/python/kernel_tests/BUILD9
-rw-r--r--tensorflow/contrib/data/python/kernel_tests/map_dataset_op_test.py52
-rw-r--r--tensorflow/contrib/data/python/kernel_tests/optimize_dataset_op_test.py51
-rw-r--r--tensorflow/contrib/data/python/ops/batching.py13
-rw-r--r--tensorflow/contrib/distribute/python/BUILD46
-rw-r--r--tensorflow/contrib/distribute/python/checkpoint_utils_test.py8
-rw-r--r--tensorflow/contrib/distribute/python/combinations.py7
-rw-r--r--tensorflow/contrib/distribute/python/cross_tower_ops.py39
-rw-r--r--tensorflow/contrib/distribute/python/estimator_integration_test.py3
-rw-r--r--tensorflow/contrib/distribute/python/examples/simple_estimator_example.py5
-rw-r--r--tensorflow/contrib/distribute/python/examples/simple_tfkeras_example.py3
-rw-r--r--tensorflow/contrib/distribute/python/keras_test.py3
-rw-r--r--tensorflow/contrib/distribute/python/mirrored_strategy.py297
-rw-r--r--tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py4
-rw-r--r--tensorflow/contrib/distribute/python/multi_worker_test_base.py38
-rw-r--r--tensorflow/contrib/distribute/python/parameter_server_strategy.py355
-rw-r--r--tensorflow/contrib/distribute/python/parameter_server_strategy_test.py455
-rw-r--r--tensorflow/contrib/distribute/python/values.py19
-rw-r--r--tensorflow/contrib/distributions/python/ops/quantized_distribution.py20
-rw-r--r--tensorflow/contrib/eager/python/examples/densenet/densenet_graph_test.py4
-rw-r--r--tensorflow/contrib/eager/python/examples/densenet/densenet_test.py42
-rw-r--r--tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb336
-rw-r--r--tensorflow/contrib/eager/python/examples/l2hmc/README.md11
-rw-r--r--tensorflow/contrib/eager/python/examples/nmt_with_attention/nmt_with_attention.ipynb15
-rw-r--r--tensorflow/contrib/eager/python/examples/notebooks/automatic_differentiation.ipynb220
-rw-r--r--tensorflow/contrib/eager/python/examples/revnet/BUILD21
-rw-r--r--tensorflow/contrib/eager/python/examples/revnet/README.md70
-rw-r--r--tensorflow/contrib/eager/python/examples/revnet/blocks.py111
-rw-r--r--tensorflow/contrib/eager/python/examples/revnet/blocks_test.py106
-rw-r--r--tensorflow/contrib/eager/python/examples/revnet/config.py6
-rw-r--r--tensorflow/contrib/eager/python/examples/revnet/imagenet_input.py230
-rw-r--r--tensorflow/contrib/eager/python/examples/revnet/main.py20
-rw-r--r--tensorflow/contrib/eager/python/examples/revnet/main_estimator.py22
-rw-r--r--tensorflow/contrib/eager/python/examples/revnet/main_estimator_tpu.py350
-rw-r--r--tensorflow/contrib/eager/python/examples/revnet/resnet_preprocessing.py190
-rw-r--r--tensorflow/contrib/eager/python/examples/revnet/revnet.py126
-rw-r--r--tensorflow/contrib/eager/python/examples/revnet/revnet_test.py26
-rw-r--r--tensorflow/contrib/eager/python/tfe.py2
-rw-r--r--tensorflow/contrib/estimator/BUILD41
-rw-r--r--tensorflow/contrib/estimator/__init__.py5
-rw-r--r--tensorflow/contrib/estimator/python/estimator/boosted_trees.py30
-rw-r--r--tensorflow/contrib/estimator/python/estimator/boosted_trees_test.py68
-rw-r--r--tensorflow/contrib/estimator/python/estimator/saved_model_estimator.py449
-rw-r--r--tensorflow/contrib/estimator/python/estimator/saved_model_estimator_test.py369
-rw-r--r--tensorflow/contrib/gan/BUILD5
-rw-r--r--tensorflow/contrib/gan/python/losses/python/tuple_losses_impl.py86
-rw-r--r--tensorflow/contrib/gan/python/losses/python/tuple_losses_test.py114
-rw-r--r--tensorflow/contrib/gan/python/train.py128
-rw-r--r--tensorflow/contrib/gan/python/train_test.py78
-rw-r--r--tensorflow/contrib/layers/__init__.py1
-rw-r--r--tensorflow/contrib/layers/python/layers/layers.py7
-rw-r--r--tensorflow/contrib/learn/python/learn/estimators/run_config.py1
-rw-r--r--tensorflow/contrib/lite/allocation.cc4
-rw-r--r--tensorflow/contrib/lite/build_def.bzl39
-rw-r--r--tensorflow/contrib/lite/builtin_op_data.h4
-rw-r--r--tensorflow/contrib/lite/builtin_ops.h1
-rw-r--r--tensorflow/contrib/lite/context.h7
-rw-r--r--tensorflow/contrib/lite/delegates/eager/BUILD37
-rw-r--r--tensorflow/contrib/lite/delegates/eager/buffer_map.cc4
-rw-r--r--tensorflow/contrib/lite/delegates/eager/delegate_data.cc3
-rw-r--r--tensorflow/contrib/lite/delegates/eager/kernel.cc289
-rw-r--r--tensorflow/contrib/lite/delegates/eager/kernel.h34
-rw-r--r--tensorflow/contrib/lite/delegates/eager/kernel_test.cc351
-rw-r--r--tensorflow/contrib/lite/delegates/nnapi/nnapi_delegate.cc211
-rw-r--r--tensorflow/contrib/lite/delegates/nnapi/nnapi_delegate_test.cc914
-rw-r--r--tensorflow/contrib/lite/experimental/c/BUILD8
-rw-r--r--tensorflow/contrib/lite/experimental/c/c_api.cc4
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/.gitignore13
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite.meta8
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples.meta8
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite.meta8
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes.meta8
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes/HelloTFLite.unity242
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes/HelloTFLite.unity.meta7
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes/add.bytesbin0 -> 476 bytes
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes/add.bytes.meta7
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scripts.meta8
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scripts/HelloTFLite.cs70
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scripts/HelloTFLite.cs.meta11
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/SDK.meta8
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/SDK/Scripts.meta8
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/SDK/Scripts/Interpreter.cs145
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/SDK/Scripts/Interpreter.cs.meta11
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/AudioManager.asset17
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/ClusterInputManager.asset6
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/DynamicsManager.asset29
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/EditorBuildSettings.asset7
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/EditorSettings.asset21
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/GraphicsSettings.asset61
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/InputManager.asset295
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/NavMeshAreas.asset91
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/NetworkManager.asset8
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/Physics2DSettings.asset37
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/ProjectSettings.asset641
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/ProjectVersion.txt1
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/QualitySettings.asset191
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/TagManager.asset43
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/TimeManager.asset9
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/UnityConnectSettings.asset34
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/README.md24
-rw-r--r--tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/UnityPackageManager/manifest.json4
-rw-r--r--tensorflow/contrib/lite/g3doc/custom_operators.md36
-rw-r--r--tensorflow/contrib/lite/g3doc/tf_ops_compatibility.md13
-rw-r--r--tensorflow/contrib/lite/interpreter.cc42
-rw-r--r--tensorflow/contrib/lite/interpreter.h10
-rw-r--r--tensorflow/contrib/lite/interpreter_test.cc17
-rw-r--r--tensorflow/contrib/lite/java/src/main/native/nativeinterpreterwrapper_jni.cc18
-rw-r--r--tensorflow/contrib/lite/kernels/BUILD29
-rw-r--r--tensorflow/contrib/lite/kernels/internal/optimized/neon_tensor_utils.h8
-rw-r--r--tensorflow/contrib/lite/kernels/internal/optimized/tensor_utils_impl.h4
-rw-r--r--tensorflow/contrib/lite/kernels/internal/reference/portable_tensor_utils.cc11
-rw-r--r--tensorflow/contrib/lite/kernels/internal/reference/portable_tensor_utils.h8
-rw-r--r--tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h48
-rw-r--r--tensorflow/contrib/lite/kernels/internal/spectrogram.cc10
-rw-r--r--tensorflow/contrib/lite/kernels/internal/tensor_utils.h8
-rw-r--r--tensorflow/contrib/lite/kernels/logical.cc121
-rw-r--r--tensorflow/contrib/lite/kernels/logical_test.cc87
-rw-r--r--tensorflow/contrib/lite/kernels/one_hot.cc199
-rw-r--r--tensorflow/contrib/lite/kernels/one_hot_test.cc182
-rw-r--r--tensorflow/contrib/lite/kernels/register.cc4
-rw-r--r--tensorflow/contrib/lite/kernels/reshape.cc17
-rw-r--r--tensorflow/contrib/lite/kernels/reshape_test.cc37
-rw-r--r--tensorflow/contrib/lite/kernels/resize_bilinear_test.cc32
-rw-r--r--tensorflow/contrib/lite/kernels/space_to_batch_nd.cc22
-rw-r--r--tensorflow/contrib/lite/kernels/space_to_batch_nd_test.cc142
-rw-r--r--tensorflow/contrib/lite/model.cc8
-rw-r--r--tensorflow/contrib/lite/model_test.cc10
-rw-r--r--tensorflow/contrib/lite/nnapi_delegate.cc1
-rw-r--r--tensorflow/contrib/lite/profiling/time.cc18
-rw-r--r--tensorflow/contrib/lite/schema/schema.fbs6
-rwxr-xr-xtensorflow/contrib/lite/schema/schema_generated.h141
-rw-r--r--tensorflow/contrib/lite/simple_memory_arena.cc1
-rw-r--r--tensorflow/contrib/lite/testing/BUILD7
-rw-r--r--tensorflow/contrib/lite/testing/generate_examples.py124
-rw-r--r--tensorflow/contrib/lite/testing/generated_examples_zip_test.cc3
-rw-r--r--tensorflow/contrib/lite/testing/tf_driver.cc2
-rw-r--r--tensorflow/contrib/lite/testing/util.h31
-rw-r--r--tensorflow/contrib/lite/toco/export_tensorflow.cc36
-rw-r--r--tensorflow/contrib/lite/toco/graph_transformations/ensure_uint8_weights_safe_for_fast_int8_kernels.cc11
-rw-r--r--tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h4
-rw-r--r--tensorflow/contrib/lite/toco/graph_transformations/propagate_array_data_types.cc20
-rw-r--r--tensorflow/contrib/lite/toco/graph_transformations/propagate_fixed_sizes.cc72
-rw-r--r--tensorflow/contrib/lite/toco/import_tensorflow.cc63
-rw-r--r--tensorflow/contrib/lite/toco/model.h116
-rw-r--r--tensorflow/contrib/lite/toco/tflite/operator.cc21
-rw-r--r--tensorflow/contrib/lite/toco/tflite/operator_test.cc10
-rw-r--r--tensorflow/contrib/lite/toco/toco_tooling.cc7
-rw-r--r--tensorflow/contrib/lite/toco/tooling_util.cc13
-rw-r--r--tensorflow/contrib/model_pruning/README.md2
-rw-r--r--tensorflow/contrib/model_pruning/python/pruning.py72
-rw-r--r--tensorflow/contrib/model_pruning/python/pruning_test.py39
-rw-r--r--tensorflow/contrib/opt/BUILD19
-rw-r--r--tensorflow/contrib/opt/__init__.py3
-rw-r--r--tensorflow/contrib/opt/python/training/shampoo.py463
-rw-r--r--tensorflow/contrib/opt/python/training/shampoo_test.py669
-rw-r--r--tensorflow/contrib/optimizer_v2/optimizer_v2_test.py40
-rw-r--r--tensorflow/contrib/recurrent/python/ops/recurrent.py2
-rw-r--r--tensorflow/contrib/saved_model/BUILD29
-rw-r--r--tensorflow/contrib/saved_model/__init__.py3
-rw-r--r--tensorflow/contrib/saved_model/python/saved_model/__init__.py1
-rw-r--r--tensorflow/contrib/saved_model/python/saved_model/keras_saved_model.py108
-rw-r--r--tensorflow/contrib/saved_model/python/saved_model/keras_saved_model_test.py201
-rw-r--r--tensorflow/contrib/tensor_forest/kernels/v4/decision_node_evaluator.cc10
-rw-r--r--tensorflow/contrib/tensorrt/BUILD12
-rw-r--r--tensorflow/contrib/tensorrt/convert/utils.cc2
-rw-r--r--tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc138
-rw-r--r--tensorflow/contrib/tensorrt/kernels/trt_engine_op.h6
-rw-r--r--tensorflow/contrib/tensorrt/tensorrt_test.cc9
-rw-r--r--tensorflow/contrib/tensorrt/test/memory_alignment_test.py72
-rw-r--r--tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py1
-rw-r--r--tensorflow/contrib/timeseries/examples/multivariate.py4
-rw-r--r--tensorflow/contrib/tpu/BUILD3
-rw-r--r--tensorflow/contrib/tpu/__init__.py2
-rw-r--r--tensorflow/contrib/tpu/profiler/tpu_profiler.proto26
-rw-r--r--tensorflow/contrib/tpu/proto/optimization_parameters.proto114
-rw-r--r--tensorflow/contrib/tpu/python/tpu/device_assignment.py2
-rw-r--r--tensorflow/contrib/tpu/python/tpu/error_handling.py32
-rw-r--r--tensorflow/contrib/tpu/python/tpu/tpu.py4
-rw-r--r--tensorflow/contrib/tpu/python/tpu/tpu_config.py33
-rw-r--r--tensorflow/contrib/tpu/python/tpu/tpu_context.py4
-rw-r--r--tensorflow/contrib/tpu/python/tpu/tpu_estimator.py239
-rw-r--r--tensorflow/contrib/tpu/python/tpu/tpu_feed.py267
-rw-r--r--tensorflow/core/BUILD11
-rw-r--r--tensorflow/core/api_def/base_api/api_def_NonMaxSuppressionV4.pbtxt78
-rw-r--r--tensorflow/core/api_def/python_api/api_def_NonMaxSuppressionV4.pbtxt4
-rw-r--r--tensorflow/core/common_runtime/broadcaster.cc247
-rw-r--r--tensorflow/core/common_runtime/broadcaster.h17
-rw-r--r--tensorflow/core/common_runtime/broadcaster_test.cc168
-rw-r--r--tensorflow/core/common_runtime/collective_param_resolver_local.cc193
-rw-r--r--tensorflow/core/common_runtime/collective_param_resolver_local.h8
-rw-r--r--tensorflow/core/common_runtime/collective_param_resolver_local_test.cc129
-rw-r--r--tensorflow/core/common_runtime/direct_session.cc8
-rw-r--r--tensorflow/core/common_runtime/eager/context.cc3
-rw-r--r--tensorflow/core/common_runtime/eager/context.h5
-rw-r--r--tensorflow/core/common_runtime/eager/execute.cc23
-rw-r--r--tensorflow/core/common_runtime/function_test.cc5
-rw-r--r--tensorflow/core/common_runtime/mkl_cpu_allocator.h1
-rw-r--r--tensorflow/core/common_runtime/placer.cc59
-rw-r--r--tensorflow/core/common_runtime/session_ref.cc170
-rw-r--r--tensorflow/core/common_runtime/session_ref.h86
-rw-r--r--tensorflow/core/common_runtime/threadpool_device_factory.cc1
-rw-r--r--tensorflow/core/distributed_runtime/rpc/grpc_server_lib.cc1
-rw-r--r--tensorflow/core/framework/bfloat16_test.cc3
-rw-r--r--tensorflow/core/framework/dataset.h53
-rw-r--r--tensorflow/core/framework/function_testlib.cc7
-rw-r--r--tensorflow/core/framework/function_testlib.h2
-rw-r--r--tensorflow/core/framework/node_def_util.cc13
-rw-r--r--tensorflow/core/framework/node_def_util.h6
-rw-r--r--tensorflow/core/framework/node_def_util_test.cc52
-rw-r--r--tensorflow/core/framework/op_compatibility_test.cc65
-rw-r--r--tensorflow/core/framework/op_kernel.cc6
-rw-r--r--tensorflow/core/framework/op_kernel.h43
-rw-r--r--tensorflow/core/framework/tensor_testutil.cc46
-rw-r--r--tensorflow/core/framework/tensor_testutil.h45
-rw-r--r--tensorflow/core/framework/tensor_testutil_test.cc357
-rw-r--r--tensorflow/core/graph/control_flow.cc37
-rw-r--r--tensorflow/core/graph/control_flow_test.cc17
-rw-r--r--tensorflow/core/graph/graph_partition.cc8
-rw-r--r--tensorflow/core/graph/mkl_graph_util.h1
-rw-r--r--tensorflow/core/graph/mkl_layout_pass.cc81
-rw-r--r--tensorflow/core/graph/mkl_layout_pass_test.cc31
-rw-r--r--tensorflow/core/graph/mkl_tfconversion_pass.cc1
-rw-r--r--tensorflow/core/graph/mkl_tfconversion_pass_test.cc1
-rw-r--r--tensorflow/core/grappler/BUILD30
-rw-r--r--tensorflow/core/grappler/clusters/cluster.cc5
-rw-r--r--tensorflow/core/grappler/clusters/cluster.h3
-rw-r--r--tensorflow/core/grappler/graph_view.cc37
-rw-r--r--tensorflow/core/grappler/graph_view.h20
-rw-r--r--tensorflow/core/grappler/mutable_graph_view.cc72
-rw-r--r--tensorflow/core/grappler/mutable_graph_view.h56
-rw-r--r--tensorflow/core/grappler/mutable_graph_view_test.cc94
-rw-r--r--tensorflow/core/grappler/op_types.cc2
-rw-r--r--tensorflow/core/grappler/op_types.h1
-rw-r--r--tensorflow/core/grappler/optimizers/BUILD40
-rw-r--r--tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc193
-rw-r--r--tensorflow/core/grappler/optimizers/arithmetic_optimizer.h1
-rw-r--r--tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc46
-rw-r--r--tensorflow/core/grappler/optimizers/constant_folding.cc83
-rw-r--r--tensorflow/core/grappler/optimizers/data/BUILD52
-rw-r--r--tensorflow/core/grappler/optimizers/data/graph_utils.cc173
-rw-r--r--tensorflow/core/grappler/optimizers/data/graph_utils.h77
-rw-r--r--tensorflow/core/grappler/optimizers/data/graph_utils_test.cc221
-rw-r--r--tensorflow/core/grappler/optimizers/data/map_and_batch_fusion.cc128
-rw-r--r--tensorflow/core/grappler/optimizers/data/map_and_batch_fusion_test.cc164
-rw-r--r--tensorflow/core/grappler/optimizers/data/map_fusion.cc261
-rw-r--r--tensorflow/core/grappler/optimizers/data/map_fusion.h47
-rw-r--r--tensorflow/core/grappler/optimizers/data/map_fusion_test.cc90
-rw-r--r--tensorflow/core/grappler/optimizers/data/noop_elimination.cc9
-rw-r--r--tensorflow/core/grappler/optimizers/data/noop_elimination_test.cc95
-rw-r--r--tensorflow/core/grappler/optimizers/data/shuffle_and_repeat_fusion.cc67
-rw-r--r--tensorflow/core/grappler/optimizers/data/shuffle_and_repeat_fusion_test.cc74
-rw-r--r--tensorflow/core/grappler/optimizers/dependency_optimizer.cc13
-rw-r--r--tensorflow/core/grappler/optimizers/evaluation_utils.cc120
-rw-r--r--tensorflow/core/grappler/optimizers/evaluation_utils.h61
-rw-r--r--tensorflow/core/grappler/optimizers/evaluation_utils_test.cc63
-rw-r--r--tensorflow/core/grappler/optimizers/loop_optimizer.cc232
-rw-r--r--tensorflow/core/grappler/optimizers/loop_optimizer.h15
-rw-r--r--tensorflow/core/grappler/optimizers/loop_optimizer_test.cc237
-rw-r--r--tensorflow/core/grappler/optimizers/meta_optimizer.cc5
-rw-r--r--tensorflow/core/grappler/utils.cc42
-rw-r--r--tensorflow/core/grappler/utils.h10
-rw-r--r--tensorflow/core/grappler/utils_test.cc6
-rw-r--r--tensorflow/core/kernels/BUILD21
-rw-r--r--tensorflow/core/kernels/batching_util/adaptive_shared_batch_scheduler.h57
-rw-r--r--tensorflow/core/kernels/cast_op.cc4
-rw-r--r--tensorflow/core/kernels/cast_op.h164
-rw-r--r--tensorflow/core/kernels/cast_op_gpu.cu.cc48
-rw-r--r--tensorflow/core/kernels/cast_op_impl.h152
-rw-r--r--tensorflow/core/kernels/cast_op_impl_bfloat.cc11
-rw-r--r--tensorflow/core/kernels/cast_op_impl_bool.cc9
-rw-r--r--tensorflow/core/kernels/cast_op_impl_complex128.cc6
-rw-r--r--tensorflow/core/kernels/cast_op_impl_complex64.cc6
-rw-r--r--tensorflow/core/kernels/cast_op_impl_double.cc9
-rw-r--r--tensorflow/core/kernels/cast_op_impl_float.cc9
-rw-r--r--tensorflow/core/kernels/cast_op_impl_half.cc6
-rw-r--r--tensorflow/core/kernels/cast_op_impl_int16.cc9
-rw-r--r--tensorflow/core/kernels/cast_op_impl_int32.cc9
-rw-r--r--tensorflow/core/kernels/cast_op_impl_int64.cc9
-rw-r--r--tensorflow/core/kernels/cast_op_impl_int8.cc9
-rw-r--r--tensorflow/core/kernels/cast_op_impl_uint16.cc9
-rw-r--r--tensorflow/core/kernels/cast_op_impl_uint32.cc9
-rw-r--r--tensorflow/core/kernels/cast_op_impl_uint64.cc9
-rw-r--r--tensorflow/core/kernels/cast_op_impl_uint8.cc9
-rw-r--r--tensorflow/core/kernels/cast_op_test.cc29
-rw-r--r--tensorflow/core/kernels/conv_ops_test.cc4
-rw-r--r--tensorflow/core/kernels/crop_and_resize_op_benchmark_test.cc72
-rw-r--r--tensorflow/core/kernels/cwise_op_tan.cc3
-rw-r--r--tensorflow/core/kernels/data/BUILD16
-rw-r--r--tensorflow/core/kernels/data/parallel_map_dataset_op.cc286
-rw-r--r--tensorflow/core/kernels/data/parallel_map_iterator.cc318
-rw-r--r--tensorflow/core/kernels/data/parallel_map_iterator.h44
-rw-r--r--tensorflow/core/kernels/fused_batch_norm_op.cc24
-rw-r--r--tensorflow/core/kernels/mkl_concat_op.cc8
-rw-r--r--tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc9
-rw-r--r--tensorflow/core/kernels/mkl_conv_grad_input_ops.cc9
-rw-r--r--tensorflow/core/kernels/mkl_conv_ops.cc17
-rw-r--r--tensorflow/core/kernels/mkl_conv_ops.h1
-rw-r--r--tensorflow/core/kernels/mkl_pooling_ops_common.h1
-rw-r--r--tensorflow/core/kernels/mkl_reshape_op.cc6
-rw-r--r--tensorflow/core/kernels/non_max_suppression_op.cc111
-rw-r--r--tensorflow/core/kernels/non_max_suppression_op_test.cc55
-rw-r--r--tensorflow/core/kernels/resource_variable_ops.cc68
-rw-r--r--tensorflow/core/kernels/save_restore_tensor.cc150
-rw-r--r--tensorflow/core/kernels/strided_slice_op.cc4
-rw-r--r--tensorflow/core/kernels/training_op_helpers.cc9
-rw-r--r--tensorflow/core/kernels/training_op_helpers.h14
-rw-r--r--tensorflow/core/lib/bfloat16/bfloat16.h20
-rw-r--r--tensorflow/core/lib/core/errors.h20
-rw-r--r--tensorflow/core/ops/compat/ops_history.v1.pbtxt64
-rw-r--r--tensorflow/core/ops/image_ops.cc39
-rw-r--r--tensorflow/core/ops/math_grad_test.cc36
-rw-r--r--tensorflow/core/ops/math_ops.cc2
-rw-r--r--tensorflow/core/ops/ops.pbtxt45
-rw-r--r--tensorflow/core/platform/cloud/BUILD68
-rw-r--r--tensorflow/core/platform/cloud/compute_engine_metadata_client.cc59
-rw-r--r--tensorflow/core/platform/cloud/compute_engine_metadata_client.h64
-rw-r--r--tensorflow/core/platform/cloud/compute_engine_metadata_client_test.cc68
-rw-r--r--tensorflow/core/platform/cloud/compute_engine_zone_provider.cc53
-rw-r--r--tensorflow/core/platform/cloud/compute_engine_zone_provider.h40
-rw-r--r--tensorflow/core/platform/cloud/compute_engine_zone_provider_test.cc69
-rw-r--r--tensorflow/core/platform/cloud/gcs_file_system.cc11
-rw-r--r--tensorflow/core/platform/cloud/gcs_file_system.h4
-rw-r--r--tensorflow/core/platform/cloud/gcs_throttle_test.cc8
-rw-r--r--tensorflow/core/platform/cloud/google_auth_provider.cc60
-rw-r--r--tensorflow/core/platform/cloud/google_auth_provider.h16
-rw-r--r--tensorflow/core/platform/cloud/google_auth_provider_test.cc42
-rw-r--r--tensorflow/core/platform/cloud/zone_provider.h48
-rw-r--r--tensorflow/core/platform/default/build_config.bzl3
-rw-r--r--tensorflow/core/platform/default/build_config_root.bzl6
-rw-r--r--tensorflow/core/platform/env_test.cc2
-rw-r--r--tensorflow/core/platform/env_time.h14
-rw-r--r--tensorflow/core/platform/gif.h4
-rw-r--r--tensorflow/core/platform/jpeg.h4
-rw-r--r--tensorflow/core/platform/png.h4
-rw-r--r--tensorflow/core/platform/posix/env_time.cc9
-rw-r--r--tensorflow/core/platform/profile_utils/cpu_utils.cc8
-rw-r--r--tensorflow/core/platform/profile_utils/cpu_utils.h7
-rw-r--r--tensorflow/core/platform/windows/env_time.cc25
-rw-r--r--tensorflow/core/protobuf/config.proto4
-rw-r--r--tensorflow/core/util/equal_graph_def_test.cc6
-rw-r--r--tensorflow/core/util/mkl_util.h22
-rw-r--r--tensorflow/docs_src/deploy/distributed.md2
-rw-r--r--tensorflow/docs_src/guide/using_gpu.md2
-rw-r--r--tensorflow/docs_src/performance/xla/broadcasting.md2
-rw-r--r--tensorflow/docs_src/performance/xla/operation_semantics.md84
-rw-r--r--tensorflow/examples/saved_model/saved_model_half_plus_two.py116
-rw-r--r--tensorflow/go/op/wrappers.go296
-rw-r--r--tensorflow/java/BUILD13
-rw-r--r--tensorflow/java/maven/README.md22
-rw-r--r--tensorflow/java/maven/hadoop/pom.xml2
-rw-r--r--tensorflow/java/maven/libtensorflow/pom.xml2
-rw-r--r--tensorflow/java/maven/libtensorflow_jni/pom.xml2
-rw-r--r--tensorflow/java/maven/libtensorflow_jni_gpu/pom.xml2
-rw-r--r--tensorflow/java/maven/pom.xml2
-rw-r--r--tensorflow/java/maven/proto/pom.xml2
-rw-r--r--tensorflow/java/maven/run_inside_container.sh68
-rw-r--r--tensorflow/java/maven/spark-connector/pom.xml2
-rw-r--r--tensorflow/java/maven/tensorflow-android/update.py17
-rw-r--r--tensorflow/java/maven/tensorflow/pom.xml2
-rw-r--r--tensorflow/java/src/gen/java/org/tensorflow/processor/OperatorProcessor.java2
-rw-r--r--tensorflow/java/src/main/java/org/tensorflow/Graph.java64
-rw-r--r--tensorflow/java/src/main/java/org/tensorflow/op/Scope.java13
-rw-r--r--tensorflow/java/src/main/java/org/tensorflow/op/core/Gradients.java48
-rw-r--r--tensorflow/java/src/main/native/graph_jni.cc21
-rw-r--r--tensorflow/java/src/main/native/graph_jni.h8
-rw-r--r--tensorflow/java/src/test/java/org/tensorflow/GraphTest.java34
-rw-r--r--tensorflow/java/src/test/java/org/tensorflow/TestUtil.java2
-rw-r--r--tensorflow/java/src/test/java/org/tensorflow/op/core/GradientsTest.java131
-rw-r--r--tensorflow/python/BUILD1
-rw-r--r--tensorflow/python/client/session.py16
-rw-r--r--tensorflow/python/client/tf_session.i1
-rw-r--r--tensorflow/python/client/tf_session_helper.cc14
-rw-r--r--tensorflow/python/client/tf_session_helper.h3
-rw-r--r--tensorflow/python/data/kernel_tests/BUILD2
-rw-r--r--tensorflow/python/data/ops/iterator_ops.py11
-rw-r--r--tensorflow/python/distribute/BUILD43
-rw-r--r--tensorflow/python/distribute/distribute_coordinator.py361
-rw-r--r--tensorflow/python/distribute/distribute_coordinator_test.py291
-rw-r--r--tensorflow/python/eager/function.py13
-rw-r--r--tensorflow/python/eager/graph_callable.py4
-rw-r--r--tensorflow/python/eager/ops_test.py4
-rw-r--r--tensorflow/python/eager/pywrap_tensor.cc1
-rw-r--r--tensorflow/python/eager/pywrap_tfe_src.cc4
-rw-r--r--tensorflow/python/estimator/BUILD3
-rw-r--r--tensorflow/python/estimator/canned/boosted_trees.py45
-rw-r--r--tensorflow/python/estimator/canned/boosted_trees_test.py3
-rw-r--r--tensorflow/python/estimator/estimator.py421
-rw-r--r--tensorflow/python/estimator/estimator_test.py13
-rw-r--r--tensorflow/python/estimator/keras.py47
-rw-r--r--tensorflow/python/estimator/keras_test.py6
-rw-r--r--tensorflow/python/estimator/model_fn.py58
-rw-r--r--tensorflow/python/estimator/run_config.py22
-rw-r--r--tensorflow/python/framework/error_interpolation.py53
-rw-r--r--tensorflow/python/framework/error_interpolation_test.py105
-rw-r--r--tensorflow/python/framework/fast_tensor_util.pyx7
-rw-r--r--tensorflow/python/framework/function.py26
-rw-r--r--tensorflow/python/framework/importer.py2
-rw-r--r--tensorflow/python/framework/importer_test.py41
-rw-r--r--tensorflow/python/framework/meta_graph.py68
-rw-r--r--tensorflow/python/framework/ops.py186
-rw-r--r--tensorflow/python/framework/ops_test.py67
-rw-r--r--tensorflow/python/framework/tensor_util.py8
-rw-r--r--tensorflow/python/framework/test_util_test.py2
-rwxr-xr-xtensorflow/python/keras/BUILD2
-rw-r--r--tensorflow/python/keras/callbacks.py73
-rw-r--r--tensorflow/python/keras/callbacks_test.py78
-rw-r--r--tensorflow/python/keras/engine/base_layer.py64
-rw-r--r--tensorflow/python/keras/engine/network.py109
-rw-r--r--tensorflow/python/keras/engine/saving_test.py41
-rw-r--r--tensorflow/python/keras/engine/topology_test.py99
-rw-r--r--tensorflow/python/keras/engine/training.py264
-rw-r--r--tensorflow/python/keras/engine/training_arrays.py18
-rw-r--r--tensorflow/python/keras/engine/training_eager.py53
-rw-r--r--tensorflow/python/keras/engine/training_eager_test.py502
-rw-r--r--tensorflow/python/keras/engine/training_test.py127
-rw-r--r--tensorflow/python/keras/engine/training_utils.py125
-rw-r--r--tensorflow/python/keras/model_subclassing_test.py3
-rw-r--r--tensorflow/python/kernel_tests/BUILD2
-rw-r--r--tensorflow/python/kernel_tests/control_flow_ops_py_test.py6
-rw-r--r--tensorflow/python/kernel_tests/conv_ops_test.py2
-rw-r--r--tensorflow/python/kernel_tests/decode_jpeg_op_test.py3
-rw-r--r--tensorflow/python/kernel_tests/depthwise_conv_op_test.py6
-rw-r--r--tensorflow/python/kernel_tests/neon_depthwise_conv_op_test.py4
-rw-r--r--tensorflow/python/kernel_tests/random/random_ops_test.py96
-rw-r--r--tensorflow/python/ops/boosted_trees_ops.py11
-rw-r--r--tensorflow/python/ops/cond_v2_impl.py10
-rw-r--r--tensorflow/python/ops/control_flow_ops.py29
-rw-r--r--tensorflow/python/ops/control_flow_util.py8
-rw-r--r--tensorflow/python/ops/image_ops_impl.py59
-rw-r--r--tensorflow/python/ops/rnn.py12
-rw-r--r--tensorflow/python/ops/rnn_cell_impl.py5
-rw-r--r--tensorflow/python/saved_model/builder_impl.py76
-rw-r--r--tensorflow/python/saved_model/constants.py6
-rw-r--r--tensorflow/python/saved_model/loader_impl.py55
-rw-r--r--tensorflow/python/saved_model/loader_test.py19
-rw-r--r--tensorflow/python/saved_model/saved_model_test.py28
-rw-r--r--tensorflow/python/summary/writer/writer.py2
-rw-r--r--tensorflow/python/tools/freeze_graph.py32
-rw-r--r--tensorflow/python/tools/freeze_graph_test.py67
-rw-r--r--tensorflow/python/training/checkpoint_utils.py36
-rw-r--r--tensorflow/python/training/checkpoint_utils_test.py4
-rw-r--r--tensorflow/python/training/checkpointable/base.py8
-rw-r--r--tensorflow/python/training/checkpointable/base_test.py20
-rw-r--r--tensorflow/python/training/checkpointable/tracking_test.py3
-rw-r--r--tensorflow/python/training/checkpointable/util.py39
-rw-r--r--tensorflow/python/training/saver.py28
-rw-r--r--tensorflow/python/training/saver_test.py31
-rw-r--r--tensorflow/python/util/function_utils.py35
-rw-r--r--tensorflow/python/util/function_utils_test.py78
-rw-r--r--tensorflow/python/util/nest.py56
-rw-r--r--tensorflow/python/util/nest_test.py29
-rw-r--r--tensorflow/stream_executor/cuda/cuda_blas.cc97
-rw-r--r--tensorflow/stream_executor/cuda/cuda_dnn.cc1
-rw-r--r--tensorflow/stream_executor/cuda/cuda_driver.cc117
-rw-r--r--tensorflow/stream_executor/module_spec.h1
-rw-r--r--tensorflow/stream_executor/stream.cc11
-rw-r--r--tensorflow/stream_executor/stream.h2
-rw-r--r--tensorflow/stream_executor/stream_test.cc139
-rw-r--r--tensorflow/tensorflow.bzl64
-rw-r--r--tensorflow/tools/api/golden/tensorflow.-config-proto.-experimental.pbtxt6
-rw-r--r--tensorflow/tools/api/golden/tensorflow.-config-proto.pbtxt6
-rw-r--r--tensorflow/tools/api/golden/tensorflow.estimator.-boosted-trees-classifier.pbtxt2
-rw-r--r--tensorflow/tools/api/golden/tensorflow.estimator.-boosted-trees-regressor.pbtxt2
-rw-r--r--tensorflow/tools/api/golden/tensorflow.estimator.-run-config.pbtxt6
-rw-r--r--tensorflow/tools/api/golden/tensorflow.image.pbtxt4
-rw-r--r--tensorflow/tools/ci_build/Dockerfile.gpu4
-rwxr-xr-xtensorflow/tools/ci_build/ci_build.sh3
-rwxr-xr-xtensorflow/tools/ci_build/ci_parameterized_build.sh54
-rw-r--r--tensorflow/tools/docker/Dockerfile.devel-cpu-mkl83
-rw-r--r--tensorflow/tools/pip_package/BUILD1
-rw-r--r--tensorflow/workspace.bzl8
890 files changed, 30988 insertions, 9873 deletions
diff --git a/tensorflow/BUILD b/tensorflow/BUILD
index cee7114d3e..529707b6fc 100644
--- a/tensorflow/BUILD
+++ b/tensorflow/BUILD
@@ -381,6 +381,14 @@ config_setting(
},
)
+# Setting to use when loading kernels dynamically
+config_setting(
+ name = "dynamic_loaded_kernels",
+ define_values = {
+ "dynamic_loaded_kernels": "true",
+ },
+)
+
config_setting(
name = "using_cuda_nvcc",
define_values = {
@@ -408,14 +416,6 @@ config_setting(
visibility = ["//visibility:public"],
)
-# TODO(laigd): consider removing this option and make TensorRT enabled
-# automatically when CUDA is enabled.
-config_setting(
- name = "with_tensorrt_support",
- values = {"define": "with_tensorrt_support=true"},
- visibility = ["//visibility:public"],
-)
-
# This flag is set from the configure step when the user selects with nGraph option.
# By default it should be false
config_setting(
diff --git a/tensorflow/c/c_api.cc b/tensorflow/c/c_api.cc
index 10bc8cdbee..19ccb6e71d 100644
--- a/tensorflow/c/c_api.cc
+++ b/tensorflow/c/c_api.cc
@@ -52,6 +52,7 @@ limitations under the License.
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/lib/core/stringpiece.h"
#include "tensorflow/core/lib/gtl/array_slice.h"
+#include "tensorflow/core/lib/strings/str_util.h"
#include "tensorflow/core/lib/strings/strcat.h"
#include "tensorflow/core/platform/mem.h"
#include "tensorflow/core/platform/mutex.h"
@@ -2389,6 +2390,12 @@ void TF_AbortWhile(const TF_WhileParams* params) { FreeWhileResources(params); }
void TF_AddGradients(TF_Graph* g, TF_Output* y, int ny, TF_Output* x, int nx,
TF_Output* dx, TF_Status* status, TF_Output* dy) {
+ TF_AddGradientsWithPrefix(g, nullptr, y, ny, x, nx, dx, status, dy);
+}
+
+void TF_AddGradientsWithPrefix(TF_Graph* g, const char* prefix, TF_Output* y,
+ int ny, TF_Output* x, int nx, TF_Output* dx,
+ TF_Status* status, TF_Output* dy) {
#ifdef __ANDROID__
status->status = tensorflow::errors::Unimplemented(
"Adding gradients is not supported in Android. File a bug at "
@@ -2405,9 +2412,29 @@ void TF_AddGradients(TF_Graph* g, TF_Output* y, int ny, TF_Output* x, int nx,
const int first_new_node_id = g->graph.num_node_ids();
+ string prefix_cmp;
+ const char* child_scope_name;
+ if (prefix == nullptr) {
+ child_scope_name = "gradients";
+ } else {
+ prefix_cmp = string(prefix) + "/";
+ // The operation should fail if the provided name prefix has already been
+ // used in this graph
+ for (const auto& pair : g->name_map) {
+ const string& name = pair.first;
+ if (name.compare(prefix) == 0 ||
+ tensorflow::str_util::StartsWith(name, prefix_cmp)) {
+ status->status = InvalidArgument(
+ "prefix [", prefix,
+ "] conflicts with existing node in the graph named [", name, "]");
+ return;
+ }
+ }
+ child_scope_name = prefix;
+ }
tensorflow::Scope scope =
NewInternalScope(&g->graph, &status->status, &g->refiner)
- .NewSubScope("gradients");
+ .NewSubScope(child_scope_name);
if (dx != nullptr) {
std::vector<tensorflow::Output> dx_arg = OutputsFromTFOutputs(dx, ny);
@@ -2422,6 +2449,18 @@ void TF_AddGradients(TF_Graph* g, TF_Output* y, int ny, TF_Output* x, int nx,
for (int i = first_new_node_id; i < g->graph.num_node_ids(); ++i) {
Node* n = g->graph.FindNodeId(i);
if (n == nullptr) continue;
+
+ // Adding the gradients to the graph can alter the prefix to prevent
+ // name collisions only if this prefix has not been provided explicitly
+ // by the user. If it was provided, assert that it remained intact.
+ if (prefix != nullptr &&
+ !tensorflow::str_util::StartsWith(n->name(), prefix_cmp)) {
+ status->status = tensorflow::errors::Internal(
+ "BUG: The gradients prefix have been unexpectedly altered when "
+ "adding the nodes to the graph. This is a bug. Please file an "
+ "issue at https://github.com/tensorflow/tensorflow/issues.");
+ return;
+ }
// We have a convoluted scheme here: Using the C++ graph construction API
// to add potentially many nodes to the graph without running the checks
// (such as uniqueness of the names of nodes) we run with other functions
diff --git a/tensorflow/c/c_api.h b/tensorflow/c/c_api.h
index c8ae6f2dd1..850f6ecd63 100644
--- a/tensorflow/c/c_api.h
+++ b/tensorflow/c/c_api.h
@@ -1131,6 +1131,7 @@ TF_CAPI_EXPORT extern void TF_AbortWhile(const TF_WhileParams* params);
// Adds operations to compute the partial derivatives of sum of `y`s w.r.t `x`s,
// i.e., d(y_1 + y_2 + ...)/dx_1, d(y_1 + y_2 + ...)/dx_2...
+//
// `dx` are used as initial gradients (which represent the symbolic partial
// derivatives of some loss function `L` w.r.t. `y`).
// `dx` must be nullptr or have size `ny`.
@@ -1139,6 +1140,12 @@ TF_CAPI_EXPORT extern void TF_AbortWhile(const TF_WhileParams* params);
// The partial derivatives are returned in `dy`. `dy` should be allocated to
// size `nx`.
//
+// Gradient nodes are automatically named under the "gradients/" prefix. To
+// guarantee name uniqueness, subsequent calls to the same graph will
+// append an incremental tag to the prefix: "gradients_1/", "gradients_2/", ...
+// See TF_AddGradientsWithPrefix, which provides a means to specify a custom
+// name prefix for operations added to a graph to compute the gradients.
+//
// WARNING: This function does not yet support all the gradients that python
// supports. See
// https://www.tensorflow.org/code/tensorflow/cc/gradients/README.md
@@ -1147,6 +1154,33 @@ TF_CAPI_EXPORT void TF_AddGradients(TF_Graph* g, TF_Output* y, int ny,
TF_Output* x, int nx, TF_Output* dx,
TF_Status* status, TF_Output* dy);
+// Adds operations to compute the partial derivatives of sum of `y`s w.r.t `x`s,
+// i.e., d(y_1 + y_2 + ...)/dx_1, d(y_1 + y_2 + ...)/dx_2...
+// This is a variant of TF_AddGradients that allows to caller to pass a custom
+// name prefix to the operations added to a graph to compute the gradients.
+//
+// `dx` are used as initial gradients (which represent the symbolic partial
+// derivatives of some loss function `L` w.r.t. `y`).
+// `dx` must be nullptr or have size `ny`.
+// If `dx` is nullptr, the implementation will use dx of `OnesLike` for all
+// shapes in `y`.
+// The partial derivatives are returned in `dy`. `dy` should be allocated to
+// size `nx`.
+// `prefix` names the scope into which all gradients operations are being added.
+// `prefix` must be unique within the provided graph otherwise this operation
+// will fail. If `prefix` is nullptr, the default prefixing behaviour takes
+// place, see TF_AddGradients for more details.
+//
+// WARNING: This function does not yet support all the gradients that python
+// supports. See
+// https://www.tensorflow.org/code/tensorflow/cc/gradients/README.md
+// for instructions on how to add C++ more gradients.
+TF_CAPI_EXPORT void TF_AddGradientsWithPrefix(TF_Graph* g, const char* prefix,
+ TF_Output* y, int ny,
+ TF_Output* x, int nx,
+ TF_Output* dx, TF_Status* status,
+ TF_Output* dy);
+
// Create a TF_Function from a TF_Graph
//
// Params:
@@ -1236,6 +1270,11 @@ TF_CAPI_EXPORT extern TF_Function* TF_GraphToFunction(
int noutputs, const TF_Output* outputs, const char* const* output_names,
const TF_FunctionOptions* opts, const char* description, TF_Status* status);
+// Returns the name of the graph function.
+// The return value points to memory that is only usable until the next
+// mutation to *func.
+TF_CAPI_EXPORT extern const char* TF_FunctionName(TF_Function* func);
+
// Write out a serialized representation of `func` (as a FunctionDef protocol
// message) to `output_func_def` (allocated by TF_NewBuffer()).
// `output_func_def`'s underlying buffer will be freed when TF_DeleteBuffer()
diff --git a/tensorflow/c/c_api_experimental.cc b/tensorflow/c/c_api_experimental.cc
index 170046c802..69b3ffe2a1 100644
--- a/tensorflow/c/c_api_experimental.cc
+++ b/tensorflow/c/c_api_experimental.cc
@@ -84,6 +84,18 @@ TF_Buffer* TF_CreateConfig(unsigned char enable_xla_compilation,
return ret;
}
+TF_Buffer* TF_CreateRunOptions(unsigned char enable_full_trace) {
+ tensorflow::RunOptions options;
+ if (enable_full_trace) {
+ options.set_trace_level(tensorflow::RunOptions::FULL_TRACE);
+ } else {
+ options.set_trace_level(tensorflow::RunOptions::NO_TRACE);
+ }
+ TF_Buffer* ret = TF_NewBuffer();
+ TF_CHECK_OK(MessageToBuffer(options, ret));
+ return ret;
+}
+
const char* TF_GraphDebugString(TF_Graph* graph, size_t* len) {
tensorflow::mutex_lock c(graph->mu);
const auto& debug_str = graph->graph.ToGraphDefDebug().DebugString();
diff --git a/tensorflow/c/c_api_experimental.h b/tensorflow/c/c_api_experimental.h
index 2d81c01e0d..6617c5a572 100644
--- a/tensorflow/c/c_api_experimental.h
+++ b/tensorflow/c/c_api_experimental.h
@@ -70,6 +70,12 @@ TF_CAPI_EXPORT extern TF_Buffer* TF_CreateConfig(
unsigned char enable_xla_compilation,
unsigned char gpu_memory_allow_growth);
+// Create a serialized tensorflow.RunOptions proto, where RunOptions.trace_level
+// is set to FULL_TRACE if `enable_full_trace` is non-zero, and NO_TRACE
+// otherwise.
+TF_CAPI_EXPORT extern TF_Buffer* TF_CreateRunOptions(
+ unsigned char enable_full_trace);
+
// Returns the graph content in a human-readable format, with length set in
// `len`. The format is subject to change in the future.
// The returned string is heap-allocated, and caller should call free() on it.
diff --git a/tensorflow/c/c_api_function.cc b/tensorflow/c/c_api_function.cc
index 384e6c8cb9..a2c5a42c11 100644
--- a/tensorflow/c/c_api_function.cc
+++ b/tensorflow/c/c_api_function.cc
@@ -536,6 +536,10 @@ TF_Function* TF_GraphToFunction(const TF_Graph* fn_body, const char* fn_name,
return tf_function;
}
+const char* TF_FunctionName(TF_Function* func) {
+ return func->fdef.signature().name().c_str();
+}
+
void TF_GraphCopyFunction(TF_Graph* g, const TF_Function* func,
const TF_Function* grad, TF_Status* status) {
if (func == nullptr) {
diff --git a/tensorflow/c/c_api_function_test.cc b/tensorflow/c/c_api_function_test.cc
index f7ca219c89..bb9433ce25 100644
--- a/tensorflow/c/c_api_function_test.cc
+++ b/tensorflow/c/c_api_function_test.cc
@@ -193,6 +193,7 @@ class CApiFunctionTest : public ::testing::Test {
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
ASSERT_NE(func_, nullptr);
+ ASSERT_EQ(std::string(func_name_), std::string(TF_FunctionName(func_)));
TF_GraphCopyFunction(host_graph_, func_, nullptr, s_);
ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
}
diff --git a/tensorflow/c/c_api_test.cc b/tensorflow/c/c_api_test.cc
index e674b1623c..aa2a537f03 100644
--- a/tensorflow/c/c_api_test.cc
+++ b/tensorflow/c/c_api_test.cc
@@ -1483,8 +1483,8 @@ class CApiGradientsTest : public ::testing::Test {
BuildSuccessGraph(inputs, outputs);
BuildExpectedGraph(grad_inputs_provided, expected_grad_outputs);
- AddGradients(grad_inputs_provided, inputs, 2, outputs, 1, grad_outputs);
-
+ AddGradients(grad_inputs_provided, nullptr, inputs, 2, outputs, 1,
+ grad_outputs);
EXPECT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
// Compare that the graphs match.
@@ -1505,7 +1505,8 @@ class CApiGradientsTest : public ::testing::Test {
BuildErrorGraph(inputs, outputs);
- AddGradients(grad_inputs_provided, inputs, 1, outputs, 1, grad_outputs);
+ AddGradients(grad_inputs_provided, nullptr, inputs, 1, outputs, 1,
+ grad_outputs);
string expected_msg =
"No gradient defined for op: TestOpWithNoGradient. Please see "
@@ -1549,19 +1550,20 @@ class CApiGradientsTest : public ::testing::Test {
EXPECT_EQ(*a_data, *b_data);
}
- void AddGradients(bool grad_inputs_provided, TF_Output* inputs, int ninputs,
- TF_Output* outputs, int noutputs, TF_Output* grad_outputs) {
+ void AddGradients(bool grad_inputs_provided, const char* prefix,
+ TF_Output* inputs, int ninputs, TF_Output* outputs,
+ int noutputs, TF_Output* grad_outputs) {
if (grad_inputs_provided) {
TF_Output grad_inputs[1];
const float grad_inputs_val[] = {1.0, 1.0, 1.0, 1.0};
TF_Operation* grad_inputs_op =
FloatConst2x2(graph_, s_, grad_inputs_val, "GradInputs");
grad_inputs[0] = TF_Output{grad_inputs_op, 0};
- TF_AddGradients(graph_, outputs, noutputs, inputs, ninputs, grad_inputs,
- s_, grad_outputs);
+ TF_AddGradientsWithPrefix(graph_, prefix, outputs, noutputs, inputs,
+ ninputs, grad_inputs, s_, grad_outputs);
} else {
- TF_AddGradients(graph_, outputs, noutputs, inputs, ninputs, nullptr, s_,
- grad_outputs);
+ TF_AddGradientsWithPrefix(graph_, prefix, outputs, noutputs, inputs,
+ ninputs, nullptr, s_, grad_outputs);
}
}
@@ -1706,6 +1708,20 @@ class CApiGradientsTest : public ::testing::Test {
return op;
}
+ void BuildGraphAndAddGradientsWithPrefixes(const char* prefix1,
+ const char* prefix2 = nullptr) {
+ TF_Output inputs[2];
+ TF_Output outputs[1];
+ TF_Output grad_outputs[2];
+
+ BuildSuccessGraph(inputs, outputs);
+
+ AddGradients(false, prefix1, inputs, 2, outputs, 1, grad_outputs);
+ if (prefix2 != nullptr) {
+ AddGradients(false, prefix2, inputs, 2, outputs, 1, grad_outputs);
+ }
+ }
+
TF_Status* s_;
TF_Graph* graph_;
TF_Graph* expected_graph_;
@@ -1725,6 +1741,56 @@ TEST_F(CApiGradientsTest, OpWithNoGradientRegistered_NoGradInputs) {
TestGradientsError(false);
}
+TEST_F(CApiGradientsTest, GradientsPrefix_PrefixIsOk) {
+ BuildGraphAndAddGradientsWithPrefixes("gradients");
+ ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
+}
+
+TEST_F(CApiGradientsTest, GradientsPrefix_TwoGradientsWithDistinctPrefixes) {
+ BuildGraphAndAddGradientsWithPrefixes("gradients", "gradients_1");
+ ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
+}
+
+TEST_F(CApiGradientsTest, GradientsPrefix_TwoGradientsInSameScope) {
+ BuildGraphAndAddGradientsWithPrefixes("scope/gradients", "scope/gradients_1");
+ ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
+}
+
+TEST_F(CApiGradientsTest, GradientsPrefix_TwoGradientsInDifferentScopes) {
+ BuildGraphAndAddGradientsWithPrefixes("scope/gradients", "scope_1/gradients");
+ ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
+}
+
+TEST_F(CApiGradientsTest, GradientsPrefix_2ndGradientsAsSubScopeOf1st) {
+ BuildGraphAndAddGradientsWithPrefixes("gradients", "gradients/sub");
+ ASSERT_EQ(TF_OK, TF_GetCode(s_)) << TF_Message(s_);
+}
+
+TEST_F(CApiGradientsTest, GradientsPrefix_PrefixMatchesExistingNodeName) {
+ BuildGraphAndAddGradientsWithPrefixes("Const_0");
+ ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
+}
+
+TEST_F(CApiGradientsTest, GradientsPrefix_TwoGradientsWithIdenticalPrefixes) {
+ BuildGraphAndAddGradientsWithPrefixes("gradients", "gradients");
+ ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
+}
+
+TEST_F(CApiGradientsTest, GradientsPrefix_2ndGradientsMatchingNodeOf1st) {
+ BuildGraphAndAddGradientsWithPrefixes("gradients", "gradients/MatMul");
+ ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
+}
+
+TEST_F(CApiGradientsTest, GradientsPrefix_1stGradientsMatchingNodeOf2nd) {
+ BuildGraphAndAddGradientsWithPrefixes("gradients/MatMul", "gradients");
+ ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
+}
+
+TEST_F(CApiGradientsTest, GradientsPrefix_2ndGradientsAsParentScopeOf1st) {
+ BuildGraphAndAddGradientsWithPrefixes("gradients/sub", "gradients");
+ ASSERT_EQ(TF_INVALID_ARGUMENT, TF_GetCode(s_)) << TF_Message(s_);
+}
+
void ScalarFloatFromTensor(const TF_Tensor* t, float* f) {
ASSERT_TRUE(t != nullptr);
ASSERT_EQ(TF_FLOAT, TF_TensorType(t));
diff --git a/tensorflow/c/eager/c_api.cc b/tensorflow/c/eager/c_api.cc
index 6c510536d6..7321b4b791 100644
--- a/tensorflow/c/eager/c_api.cc
+++ b/tensorflow/c/eager/c_api.cc
@@ -288,7 +288,7 @@ TFE_Context* TFE_NewContext(const TFE_ContextOptions* opts, TF_Status* status) {
opts->async, std::move(device_mgr), r);
}
-void TFE_DeleteContext(TFE_Context* ctx, TF_Status* status) { delete ctx; }
+void TFE_DeleteContext(TFE_Context* ctx) { delete ctx; }
TF_DeviceList* TFE_ContextListDevices(TFE_Context* ctx, TF_Status* status) {
TF_DeviceList* list = new TF_DeviceList;
@@ -336,7 +336,7 @@ TFE_TensorHandle* TFE_NewTensorHandle(TF_Tensor* t, TF_Status* status) {
}
void TFE_DeleteTensorHandle(TFE_TensorHandle* h) {
- DCHECK(h);
+ if (h == nullptr) return;
if (h->handle) {
h->handle->Unref();
}
diff --git a/tensorflow/c/eager/c_api.h b/tensorflow/c/eager/c_api.h
index fdbd5374b2..ea019a5711 100644
--- a/tensorflow/c/eager/c_api.h
+++ b/tensorflow/c/eager/c_api.h
@@ -102,8 +102,7 @@ typedef struct TFE_Context TFE_Context;
TF_CAPI_EXPORT extern TFE_Context* TFE_NewContext(
const TFE_ContextOptions* opts, TF_Status* status);
-TF_CAPI_EXPORT extern void TFE_DeleteContext(TFE_Context* ctx,
- TF_Status* status);
+TF_CAPI_EXPORT extern void TFE_DeleteContext(TFE_Context* ctx);
TF_CAPI_EXPORT extern TF_DeviceList* TFE_ContextListDevices(TFE_Context* ctx,
TF_Status* status);
diff --git a/tensorflow/c/eager/c_api_test.cc b/tensorflow/c/eager/c_api_test.cc
index 3504a8b5e7..0bdea70fe6 100644
--- a/tensorflow/c/eager/c_api_test.cc
+++ b/tensorflow/c/eager/c_api_test.cc
@@ -49,7 +49,7 @@ void BM_InitOp(int iters) {
}
tensorflow::testing::StopTiming();
TFE_DeleteTensorHandle(m);
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
TF_DeleteStatus(status);
}
@@ -80,7 +80,7 @@ void BM_Execute(int iters, int async) {
tensorflow::testing::StopTiming();
TFE_DeleteOp(matmul);
TFE_DeleteTensorHandle(m);
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
TF_DeleteStatus(status);
}
@@ -95,7 +95,7 @@ TEST(CAPI, Context) {
TF_DeviceList* devices = TFE_ContextListDevices(ctx, status);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
const int num_devices = TF_DeviceListCount(devices);
@@ -195,7 +195,7 @@ void TestRemoteExecute(bool async) {
TFE_DeleteOp(matmul);
TFE_ContextAsyncWait(ctx, status);
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
TF_DeleteStatus(status);
@@ -281,7 +281,7 @@ void TestRemoteExecuteSilentCopies(bool async) {
TFE_DeleteOp(matmul);
TFE_ContextAsyncWait(ctx, status);
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
TF_DeleteStatus(status);
@@ -380,8 +380,7 @@ void TensorHandleCopyBetweenDevices(bool async) {
TF_DeleteDeviceList(devices);
TF_DeleteTensor(t);
TFE_DeleteTensorHandle(hcpu);
- TFE_DeleteContext(ctx, status.get());
- EXPECT_EQ(TF_OK, TF_GetCode(status.get())) << TF_Message(status.get());
+ TFE_DeleteContext(ctx);
}
TEST(CAPI, TensorHandleCopyBetweenDevices) {
@@ -418,7 +417,7 @@ void TensorHandleCopyBetweenDevicesError(bool async) {
TFE_DeleteTensorHandle(hcopy);
TFE_DeleteTensorHandle(hcpu);
if (hdevice != nullptr) TFE_DeleteTensorHandle(hdevice);
- TFE_DeleteContext(ctx, status.get());
+ TFE_DeleteContext(ctx);
}
TEST(CAPI, TensorHandleCopyBetweenDevicesError) {
@@ -451,7 +450,7 @@ void TensorHandleCopyBetweenTwoGPUDevices(bool async) {
TF_DeleteDeviceList(devices);
TF_DeleteTensor(t);
TFE_DeleteTensorHandle(hcpu);
- TFE_DeleteContext(ctx, status.get());
+ TFE_DeleteContext(ctx);
return;
}
const string gpu_1_name(TF_DeviceListName(devices, 1, status.get()));
@@ -484,8 +483,7 @@ void TensorHandleCopyBetweenTwoGPUDevices(bool async) {
TF_DeleteDeviceList(devices);
TF_DeleteTensor(t);
TFE_DeleteTensorHandle(hcpu);
- TFE_DeleteContext(ctx, status.get());
- EXPECT_EQ(TF_OK, TF_GetCode(status.get())) << TF_Message(status.get());
+ TFE_DeleteContext(ctx);
}
TEST(CAPI, TensorHandleCopyBetweenTwoGPUDevices) {
@@ -533,8 +531,7 @@ void TensorHandleSilentCopy(bool async) {
TFE_DeleteTensorHandle(hcpu);
TFE_ContextAsyncWait(ctx, status.get());
EXPECT_EQ(TF_OK, TF_GetCode(status.get())) << TF_Message(status.get());
- TFE_DeleteContext(ctx, status.get());
- EXPECT_EQ(TF_OK, TF_GetCode(status.get())) << TF_Message(status.get());
+ TFE_DeleteContext(ctx);
}
TEST(CAPI, TensorHandleSilentCopy) { TensorHandleSilentCopy(false); }
@@ -580,8 +577,7 @@ void TensorHandleSilentCopyLocal(bool async) {
TFE_DeleteTensorHandle(hcpu);
TFE_ContextAsyncWait(ctx, status.get());
EXPECT_EQ(TF_OK, TF_GetCode(status.get())) << TF_Message(status.get());
- TFE_DeleteContext(ctx, status.get());
- EXPECT_EQ(TF_OK, TF_GetCode(status.get())) << TF_Message(status.get());
+ TFE_DeleteContext(ctx);
}
TEST(CAPI, TensorHandleSilentCopyLocal) { TensorHandleSilentCopyLocal(false); }
TEST(CAPI, TensorHandleSilentCopyLocalAsync) {
@@ -614,7 +610,7 @@ void SetAndGetOpDevices(bool async) {
TFE_DeleteOp(matmul);
TFE_DeleteTensorHandle(m);
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
TF_DeleteStatus(status);
}
@@ -640,7 +636,7 @@ void Execute_MatMul_CPU(bool async) {
TF_Tensor* t = TFE_TensorHandleResolve(retvals[0], status);
ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
TFE_DeleteTensorHandle(retvals[0]);
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
float product[4] = {0};
EXPECT_EQ(sizeof(product), TF_TensorByteSize(t));
@@ -712,7 +708,7 @@ void Execute_MatMul_CPU_Runtime_Error(bool async) {
TFE_DeleteTensorHandle(m1);
TFE_DeleteTensorHandle(m2);
TFE_DeleteTensorHandle(retvals[0]);
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
TF_DeleteStatus(status);
}
TEST(CAPI, Execute_MatMul_CPU_Runtime_Error) {
@@ -743,7 +739,7 @@ void Execute_MatMul_CPU_Type_Error(bool async) {
if (retvals[0] != nullptr) {
TFE_DeleteTensorHandle(retvals[0]);
}
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
TF_DeleteStatus(status);
}
@@ -781,7 +777,7 @@ TEST(CAPI, Execute_Min_CPU) {
TF_DeleteTensor(t);
EXPECT_EQ(1, output[0]);
EXPECT_EQ(3, output[1]);
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
TF_DeleteStatus(status);
}
@@ -823,7 +819,7 @@ void Execute_MatMul_XLA_CPU(bool async) {
EXPECT_EQ(10, product[1]);
EXPECT_EQ(15, product[2]);
EXPECT_EQ(22, product[3]);
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
TF_DeleteStatus(status);
}
TEST(CAPI, Execute_MatMul_XLA_CPU) { Execute_MatMul_XLA_CPU(false); }
@@ -862,7 +858,7 @@ void Execute_Min_XLA_CPU(bool async) {
TF_DeleteTensor(t);
EXPECT_EQ(1, output[0]);
EXPECT_EQ(3, output[1]);
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
TF_DeleteStatus(status);
}
TEST(CAPI, Execute_Min_XLA_CPU) { Execute_Min_XLA_CPU(false); }
@@ -898,7 +894,7 @@ void ExecuteWithTracing(bool async) {
TF_Tensor* t = TFE_TensorHandleResolve(retvals[0], status);
TFE_DeleteTensorHandle(retvals[0]);
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
ASSERT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
float product[4] = {0};
EXPECT_EQ(sizeof(product), TF_TensorByteSize(t));
@@ -974,7 +970,7 @@ TEST(CAPI, Function_ident_CPU) {
TF_DeleteTensor(r);
TFE_DeleteTensorHandle(result[0]);
}
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
ASSERT_TRUE(TF_GetCode(status) == TF_OK) << TF_Message(status);
TF_DeleteStatus(status);
}
@@ -1044,7 +1040,7 @@ TEST(CAPI, Function_ident_XLA_CPU) {
TF_DeleteTensor(r);
TFE_DeleteTensorHandle(result[0]);
}
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
ASSERT_TRUE(TF_GetCode(status) == TF_OK) << TF_Message(status);
TF_DeleteStatus(status);
}
@@ -1120,7 +1116,7 @@ void FunctionDefAndExecute(bool async) {
EXPECT_EQ(10, product[1]);
EXPECT_EQ(15, product[2]);
EXPECT_EQ(22, product[3]);
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
TF_DeleteStatus(status);
}
@@ -1161,7 +1157,7 @@ void BM_ExecuteFunction(int iters, int async) {
tensorflow::testing::StopTiming();
TFE_DeleteTensorHandle(m);
TFE_DeleteTensorHandle(retval[0]);
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
EXPECT_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
TF_DeleteStatus(status);
}
@@ -1249,7 +1245,7 @@ TEST(CAPI, Variables) {
TFE_DeleteTensorHandle(var_handle);
TFE_DeleteTensorHandle(value_handle);
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
TF_DeleteStatus(status);
}
@@ -1288,7 +1284,7 @@ void BM_ReadVariable(int iters) {
TFE_DeleteOp(op);
TFE_DeleteTensorHandle(var_handle);
- TFE_DeleteContext(ctx, status);
+ TFE_DeleteContext(ctx);
CHECK_EQ(TF_OK, TF_GetCode(status)) << TF_Message(status);
TF_DeleteStatus(status);
}
diff --git a/tensorflow/cc/gradients/math_grad_test.cc b/tensorflow/cc/gradients/math_grad_test.cc
index fd7b6fe662..1c9bdff5e1 100644
--- a/tensorflow/cc/gradients/math_grad_test.cc
+++ b/tensorflow/cc/gradients/math_grad_test.cc
@@ -475,11 +475,7 @@ TEST_F(CWiseUnaryGradTest, Tan_Complex) {
auto x_fn = [this](const int i) {
return CRV({{1, 0}, {0, 1}, {2, -1}, {1, 2}, {3, 4}});
};
- // TODO(kbsriram)
- // Enable when tan kernel supports complex inputs
- if (false) {
- TestCWiseGrad<complex64, complex64>(TAN, x_fn);
- }
+ TestCWiseGrad<complex64, complex64>(TAN, x_fn);
}
TEST_F(CWiseUnaryGradTest, Atan) {
diff --git a/tensorflow/cc/saved_model/loader.cc b/tensorflow/cc/saved_model/loader.cc
index 07807ed2f3..98be66a6ad 100644
--- a/tensorflow/cc/saved_model/loader.cc
+++ b/tensorflow/cc/saved_model/loader.cc
@@ -74,6 +74,54 @@ void AddAssetsTensorsToInputs(const StringPiece export_dir,
}
}
+// Like Session::Run(), but uses the Make/Run/ReleaseCallable() API to avoid
+// leaving behind non-GC'ed state.
+//
+// Detailed motivation behind this approach, from ashankar@:
+//
+// Each call to Session::Run() that identifies a new subgraph (based on feeds
+// and fetches) creates some datastructures that live as long as the session
+// (the partitioned graph, associated executors etc.).
+//
+// A pathological case of this would be if say the initialization op
+// (main_op/legacy_init_op) involves the use of a large constant. Then we
+// allocate memory for that large constant that will just stick around till the
+// session dies. With this Callable mechanism, that memory will be released
+// right after ReleaseCallable returns.
+//
+// However, the resource manager state remains.
+Status RunOnce(const RunOptions& run_options,
+ const std::vector<std::pair<string, Tensor>>& inputs,
+ const std::vector<string>& output_tensor_names,
+ const std::vector<string>& target_node_names,
+ std::vector<Tensor>* outputs, RunMetadata* run_metadata,
+ Session* session) {
+ CallableOptions callable_options;
+ std::vector<Tensor> feed_tensors;
+ *callable_options.mutable_run_options() = run_options;
+ for (const auto& input : inputs) {
+ const string& name = input.first;
+ const Tensor& tensor = input.second;
+ callable_options.add_feed(name);
+ feed_tensors.push_back(tensor);
+ }
+ for (const string& output_tensor_name : output_tensor_names) {
+ callable_options.add_fetch(output_tensor_name);
+ }
+ for (const string& target_node_name : target_node_names) {
+ callable_options.add_target(target_node_name);
+ }
+
+ Session::CallableHandle callable_handle;
+ TF_RETURN_IF_ERROR(session->MakeCallable(callable_options, &callable_handle));
+ const Status run_status = session->RunCallable(callable_handle, feed_tensors,
+ outputs, run_metadata);
+ // Be sure to call ReleaseCallable() regardless of the outcome of
+ // RunCallable().
+ session->ReleaseCallable(callable_handle).IgnoreError();
+ return run_status;
+}
+
bool HasMainOp(const MetaGraphDef& meta_graph_def) {
const auto& collection_def_map = meta_graph_def.collection_def();
if (collection_def_map.find(kSavedModelMainOpKey) !=
@@ -86,10 +134,11 @@ bool HasMainOp(const MetaGraphDef& meta_graph_def) {
Status RunMainOp(const RunOptions& run_options, const string& export_dir,
const MetaGraphDef& meta_graph_def,
const std::vector<AssetFileDef>& asset_file_defs,
- Session* session) {
- LOG(INFO) << "Running MainOp on SavedModel bundle.";
+ Session* session, const string& main_op_key) {
+ LOG(INFO) << "Running MainOp with key " << main_op_key
+ << " on SavedModel bundle.";
const auto& collection_def_map = meta_graph_def.collection_def();
- const auto main_op_it = collection_def_map.find(kSavedModelMainOpKey);
+ const auto main_op_it = collection_def_map.find(main_op_key);
if (main_op_it != collection_def_map.end()) {
if (main_op_it->second.node_list().value_size() != 1) {
return errors::FailedPrecondition(
@@ -99,8 +148,8 @@ Status RunMainOp(const RunOptions& run_options, const string& export_dir,
AddAssetsTensorsToInputs(export_dir, asset_file_defs, &inputs);
RunMetadata run_metadata;
const StringPiece main_op_name = main_op_it->second.node_list().value(0);
- return session->Run(run_options, inputs, {}, {main_op_name.ToString()},
- nullptr /* outputs */, &run_metadata);
+ return RunOnce(run_options, inputs, {}, {main_op_name.ToString()},
+ nullptr /* outputs */, &run_metadata, session);
}
return Status::OK();
}
@@ -137,32 +186,8 @@ Status RunRestore(const RunOptions& run_options, const string& export_dir,
AddAssetsTensorsToInputs(export_dir, asset_file_defs, &inputs);
RunMetadata run_metadata;
- return session->Run(run_options, inputs, {}, {restore_op_name.ToString()},
- nullptr /* outputs */, &run_metadata);
-}
-
-Status RunLegacyInitOp(const RunOptions& run_options, const string& export_dir,
- const MetaGraphDef& meta_graph_def,
- const std::vector<AssetFileDef>& asset_file_defs,
- Session* session) {
- LOG(INFO) << "Running LegacyInitOp on SavedModel bundle.";
- const auto& collection_def_map = meta_graph_def.collection_def();
- const auto init_op_it = collection_def_map.find(kSavedModelLegacyInitOpKey);
- if (init_op_it != collection_def_map.end()) {
- if (init_op_it->second.node_list().value_size() != 1) {
- return errors::FailedPrecondition(strings::StrCat(
- "Expected exactly one serving init op in : ", export_dir));
- }
- std::vector<std::pair<string, Tensor>> inputs;
- AddAssetsTensorsToInputs(export_dir, asset_file_defs, &inputs);
- RunMetadata run_metadata;
- const StringPiece legacy_init_op_name =
- init_op_it->second.node_list().value(0);
- return session->Run(run_options, inputs, {},
- {legacy_init_op_name.ToString()}, nullptr /* outputs */,
- &run_metadata);
- }
- return Status::OK();
+ return RunOnce(run_options, inputs, {}, {restore_op_name.ToString()},
+ nullptr /* outputs */, &run_metadata, session);
}
Status GetAssetFileDefs(const MetaGraphDef& meta_graph_def,
@@ -204,11 +229,11 @@ Status LoadSavedModelInternal(const SessionOptions& session_options,
if (HasMainOp(bundle->meta_graph_def)) {
TF_RETURN_IF_ERROR(RunMainOp(run_options, export_dir,
bundle->meta_graph_def, asset_file_defs,
- bundle->session.get()));
+ bundle->session.get(), kSavedModelMainOpKey));
} else {
- TF_RETURN_IF_ERROR(RunLegacyInitOp(run_options, export_dir,
- bundle->meta_graph_def, asset_file_defs,
- bundle->session.get()));
+ TF_RETURN_IF_ERROR(RunMainOp(
+ run_options, export_dir, bundle->meta_graph_def, asset_file_defs,
+ bundle->session.get(), kSavedModelLegacyInitOpKey));
}
return Status::OK();
}
diff --git a/tensorflow/compiler/aot/BUILD b/tensorflow/compiler/aot/BUILD
index 2119c8ec47..fef8b8d4d4 100644
--- a/tensorflow/compiler/aot/BUILD
+++ b/tensorflow/compiler/aot/BUILD
@@ -68,6 +68,7 @@ cc_library(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:compile_only_client",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/service:compiler",
"//tensorflow/compiler/xla/service/cpu:cpu_compiler",
"//tensorflow/core:core_cpu_internal",
diff --git a/tensorflow/compiler/aot/compile.cc b/tensorflow/compiler/aot/compile.cc
index bbc35da2ef..2b5f97b34c 100644
--- a/tensorflow/compiler/aot/compile.cc
+++ b/tensorflow/compiler/aot/compile.cc
@@ -25,6 +25,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/tf2xla_util.h"
#include "tensorflow/compiler/xla/client/client_library.h"
#include "tensorflow/compiler/xla/client/compile_only_client.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/service/cpu/cpu_compiler.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/compiler/xla/util.h"
diff --git a/tensorflow/compiler/jit/BUILD b/tensorflow/compiler/jit/BUILD
index 9174a67cc6..e34347b9d4 100644
--- a/tensorflow/compiler/jit/BUILD
+++ b/tensorflow/compiler/jit/BUILD
@@ -166,6 +166,7 @@ cc_library(
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/service:stream_pool",
"//tensorflow/core:core_cpu",
"//tensorflow/core:core_cpu_internal",
"//tensorflow/core:framework",
diff --git a/tensorflow/compiler/jit/kernels/xla_launch_op.cc b/tensorflow/compiler/jit/kernels/xla_launch_op.cc
index c5d0e4f8fb..b313d48011 100644
--- a/tensorflow/compiler/jit/kernels/xla_launch_op.cc
+++ b/tensorflow/compiler/jit/kernels/xla_launch_op.cc
@@ -153,6 +153,10 @@ void XlaLocalLaunchBase::Compute(OpKernelContext* ctx) {
XlaCompiler::Options options;
options.client = client;
+ if (ctx->op_device_context() != nullptr) {
+ options.device_ordinal =
+ ctx->op_device_context()->stream()->parent()->device_ordinal();
+ }
options.device_type = cache->device_type();
options.flib_def = ctx->function_library()->GetFunctionLibraryDefinition();
options.graph_def_version = ctx->function_library()->graph_def_version();
diff --git a/tensorflow/compiler/jit/xla_compilation_cache.cc b/tensorflow/compiler/jit/xla_compilation_cache.cc
index 54a41a4daa..08c357c879 100644
--- a/tensorflow/compiler/jit/xla_compilation_cache.cc
+++ b/tensorflow/compiler/jit/xla_compilation_cache.cc
@@ -209,7 +209,9 @@ Status XlaCompilationCache::BuildExecutable(
argument_layouts[i] = &result.xla_input_shapes[i];
}
xla::ExecutableBuildOptions build_options;
- build_options.set_device_ordinal(client_->default_device_ordinal());
+ build_options.set_device_ordinal(options.device_ordinal != -1
+ ? options.device_ordinal
+ : client_->default_device_ordinal());
build_options.set_result_layout(result.xla_output_shape);
build_options.set_device_allocator(options.device_allocator);
diff --git a/tensorflow/compiler/jit/xla_device.h b/tensorflow/compiler/jit/xla_device.h
index fccdb14368..4a5942fbd7 100644
--- a/tensorflow/compiler/jit/xla_device.h
+++ b/tensorflow/compiler/jit/xla_device.h
@@ -29,6 +29,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_compiler.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/local_client.h"
+#include "tensorflow/compiler/xla/service/stream_pool.h"
#include "tensorflow/core/common_runtime/device_factory.h"
#include "tensorflow/core/common_runtime/local_device.h"
#include "tensorflow/core/framework/allocator.h"
@@ -153,17 +154,17 @@ class XlaDevice : public LocalDevice {
// stream are executed on the device. Operations include data
// copying back and forth between CPU and the device, and
// computations enqueued by XLA.
- xla::Backend::StreamPtr stream_;
+ xla::StreamPool::Ptr stream_;
// If true, only stream_ is valid and all computation and transfers use
// stream_. If false, computation is performed by stream_ and transfers are
// performed by host_to_device/device_to_host_stream.
bool use_multiple_streams_;
// If use_multiple_streams_, host to device transfers are performed using this
// stream.
- xla::Backend::StreamPtr host_to_device_stream_;
+ xla::StreamPool::Ptr host_to_device_stream_;
// If use_multiple_streams_, device to host transfers are performed using this
// stream.
- xla::Backend::StreamPtr device_to_host_stream_;
+ xla::StreamPool::Ptr device_to_host_stream_;
// Must we use XLA's transfer manager for correct host<->device transfers? if
// false, we can use ThenMemcpy() instead.
bool transfer_as_literal_;
diff --git a/tensorflow/compiler/tests/BUILD b/tensorflow/compiler/tests/BUILD
index 080bed50e6..b7dc5d4c74 100644
--- a/tensorflow/compiler/tests/BUILD
+++ b/tensorflow/compiler/tests/BUILD
@@ -673,6 +673,7 @@ tf_xla_py_test(
"cpu",
"cpu_ondemand",
],
+ shard_count = 5,
tags = ["optonly"],
deps = [
":xla_test",
@@ -1002,6 +1003,7 @@ tf_xla_py_test(
name = "sort_ops_test",
size = "medium",
srcs = ["sort_ops_test.py"],
+ shard_count = 5,
# Times out in fastbuild mode.
tags = ["optonly"],
deps = [
diff --git a/tensorflow/compiler/tests/image_ops_test.py b/tensorflow/compiler/tests/image_ops_test.py
index 8b01ef96db..bf986ade06 100644
--- a/tensorflow/compiler/tests/image_ops_test.py
+++ b/tensorflow/compiler/tests/image_ops_test.py
@@ -26,6 +26,7 @@ import numpy as np
from six.moves import xrange # pylint: disable=redefined-builtin
from tensorflow.compiler.tests import xla_test
+from tensorflow.python.compat import compat
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import ops
from tensorflow.python.ops import array_ops
@@ -579,5 +580,140 @@ class ResizeBilinearTest(xla_test.XLATestCase):
large_tolerance=True)
+class NonMaxSuppressionTest(xla_test.XLATestCase):
+
+ def testNMS128From1024(self):
+ # TODO(b/26783907): The Sort HLO is not implemented on CPU or GPU.
+ if self.device in ["XLA_CPU", "XLA_GPU"]:
+ return
+
+ with compat.forward_compatibility_horizon(2018, 8, 8):
+ num_boxes = 1024
+ boxes_np = np.random.normal(50, 10, (num_boxes, 4)).astype("f4")
+ scores_np = np.random.normal(0.5, 0.1, (num_boxes,)).astype("f4")
+
+ max_output_size = 128
+ iou_threshold_np = np.array(0.5, dtype=np.float32)
+ score_threshold_np = np.array(0.0, dtype=np.float32)
+
+ with self.test_session() as sess:
+ boxes = array_ops.placeholder(boxes_np.dtype, shape=boxes_np.shape)
+ scores = array_ops.placeholder(scores_np.dtype, shape=scores_np.shape)
+ iou_threshold = array_ops.placeholder(iou_threshold_np.dtype,
+ iou_threshold_np.shape)
+ score_threshold = array_ops.placeholder(score_threshold_np.dtype,
+ score_threshold_np.shape)
+ with self.test_scope():
+ selected_indices = image_ops.non_max_suppression_padded(
+ boxes=boxes,
+ scores=scores,
+ max_output_size=max_output_size,
+ iou_threshold=iou_threshold,
+ score_threshold=score_threshold,
+ pad_to_max_output_size=True)
+ inputs_feed = {
+ boxes: boxes_np,
+ scores: scores_np,
+ score_threshold: score_threshold_np,
+ iou_threshold: iou_threshold_np
+ }
+ (indices_tf, _) = sess.run(selected_indices, feed_dict=inputs_feed)
+
+ self.assertEqual(indices_tf.size, max_output_size)
+
+ def testNMS3From6Boxes(self):
+ # TODO(b/26783907): The Sort HLO is not implemented on CPU or GPU.
+ if self.device in ["XLA_CPU", "XLA_GPU"]:
+ return
+
+ with compat.forward_compatibility_horizon(2018, 8, 8):
+ # Three boxes are selected based on IOU.
+ boxes_data = [[0, 0, 1, 1], [0, 0.1, 1, 1.1], [0, -0.1, 1, 0.9],
+ [0, 10, 1, 11], [0, 10.1, 1, 11.1], [0, 100, 1, 101]]
+ boxes_np = np.array(boxes_data, dtype=np.float32)
+
+ scores_data = [0.9, 0.75, 0.6, 0.95, 0.5, 0.3]
+ scores_np = np.array(scores_data, dtype=np.float32)
+
+ max_output_size = 3
+ iou_threshold_np = np.array(0.5, dtype=np.float32)
+ score_threshold_np = np.array(0.0, dtype=np.float32)
+
+ with self.test_session() as sess:
+ boxes = array_ops.placeholder(boxes_np.dtype, shape=boxes_np.shape)
+ scores = array_ops.placeholder(scores_np.dtype, shape=scores_np.shape)
+ iou_threshold = array_ops.placeholder(iou_threshold_np.dtype,
+ iou_threshold_np.shape)
+ score_threshold = array_ops.placeholder(score_threshold_np.dtype,
+ score_threshold_np.shape)
+ with self.test_scope():
+ selected_indices = image_ops.non_max_suppression_padded(
+ boxes=boxes,
+ scores=scores,
+ max_output_size=max_output_size,
+ iou_threshold=iou_threshold,
+ score_threshold=score_threshold,
+ pad_to_max_output_size=True)
+ inputs_feed = {
+ boxes: boxes_np,
+ scores: scores_np,
+ score_threshold: score_threshold_np,
+ iou_threshold: iou_threshold_np
+ }
+ (indices_tf, num_valid) = sess.run(
+ selected_indices, feed_dict=inputs_feed)
+
+ self.assertEqual(indices_tf.size, max_output_size)
+ self.assertEqual(num_valid, 3)
+ self.assertAllClose(indices_tf[:num_valid], [3, 0, 5])
+
+ def testNMS3Then2WithScoreThresh(self):
+ # Three boxes are selected based on IOU.
+ # One is filtered out by score threshold.
+
+ # TODO(b/26783907): The Sort HLO is not implemented on CPU or GPU.
+ if self.device in ["XLA_CPU", "XLA_GPU"]:
+ return
+
+ with compat.forward_compatibility_horizon(2018, 8, 8):
+ boxes_data = [[0, 0, 1, 1], [0, 0.1, 1, 1.1], [0, -0.1, 1, 0.9],
+ [0, 10, 1, 11], [0, 10.1, 1, 11.1], [0, 100, 1, 101]]
+ boxes_np = np.array(boxes_data, dtype=np.float32)
+
+ scores_data = [0.9, 0.75, 0.6, 0.95, 0.5, 0.3]
+ scores_np = np.array(scores_data, dtype=np.float32)
+ max_output_size = 3
+ iou_threshold_np = np.array(0.5, dtype=np.float32)
+ score_threshold_np = np.array(0.4, dtype=np.float32)
+
+ with self.test_session() as sess:
+ boxes = array_ops.placeholder(boxes_np.dtype, shape=boxes_np.shape)
+ scores = array_ops.placeholder(scores_np.dtype, shape=scores_np.shape)
+ iou_threshold = array_ops.placeholder(iou_threshold_np.dtype,
+ iou_threshold_np.shape)
+ score_threshold = array_ops.placeholder(score_threshold_np.dtype,
+ score_threshold_np.shape)
+ with self.test_scope():
+ selected_indices = image_ops.non_max_suppression_padded(
+ boxes=boxes,
+ scores=scores,
+ max_output_size=max_output_size,
+ iou_threshold=iou_threshold,
+ score_threshold=score_threshold,
+ pad_to_max_output_size=True)
+ inputs_feed = {
+ boxes: boxes_np,
+ scores: scores_np,
+ iou_threshold: iou_threshold_np,
+ score_threshold: score_threshold_np
+ }
+ (indices_tf, num_valid) = sess.run(
+ selected_indices, feed_dict=inputs_feed)
+
+ self.assertEqual(indices_tf.size, max_output_size)
+ self.assertEqual(num_valid, 2)
+ self.assertAllClose(indices_tf[:num_valid], [3, 0])
+
+
if __name__ == "__main__":
test.main()
diff --git a/tensorflow/compiler/tf2xla/BUILD b/tensorflow/compiler/tf2xla/BUILD
index ff002d15b0..338943201b 100644
--- a/tensorflow/compiler/tf2xla/BUILD
+++ b/tensorflow/compiler/tf2xla/BUILD
@@ -81,7 +81,7 @@ cc_library(
"//tensorflow/compiler/tf2xla/kernels:xla_cpu_only_ops",
"//tensorflow/compiler/tf2xla/kernels:xla_ops",
"//tensorflow/compiler/xla/client",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/core:core_cpu",
"//tensorflow/core:core_cpu_internal",
"//tensorflow/core:framework",
@@ -119,6 +119,7 @@ cc_library(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/service:cpu_plugin",
"//tensorflow/compiler/xla/service/cpu:cpu_executable",
"//tensorflow/core:lib",
@@ -139,14 +140,12 @@ cc_library(
"xla_op_registry.cc",
"xla_resource.cc",
"xla_cpu_backend.cc",
- "legacy_flags/backend_registration_flags.cc",
] + if_cuda_is_configured([
"xla_gpu_backend.cc",
]),
hdrs = [
"const_analysis.h",
"graph_compiler.h",
- "legacy_flags/backend_registration_flags.h",
"xla_compilation_device.h",
"xla_compiler.h",
"xla_context.h",
@@ -172,16 +171,14 @@ cc_library(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:arithmetic",
"//tensorflow/compiler/xla/client/lib:constants",
"//tensorflow/compiler/xla/client/lib:numeric",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
- "//tensorflow/compiler/xla/legacy_flags:parse_flags_from_env",
"//tensorflow/core:core_cpu",
"//tensorflow/core:core_cpu_internal",
"//tensorflow/core:framework",
- "//tensorflow/core:framework_internal",
"//tensorflow/core:lib",
"//tensorflow/core:lib_internal",
"//tensorflow/core:protos_all_cc",
@@ -294,6 +291,7 @@ tf_cc_test(
"//tensorflow/compiler/xla:statusor",
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/service:cpu_plugin",
"//tensorflow/core:framework",
"//tensorflow/core:lib",
diff --git a/tensorflow/compiler/tf2xla/dump_graph.cc b/tensorflow/compiler/tf2xla/dump_graph.cc
index 03603ee9ba..24616c01c7 100644
--- a/tensorflow/compiler/tf2xla/dump_graph.cc
+++ b/tensorflow/compiler/tf2xla/dump_graph.cc
@@ -33,7 +33,7 @@ struct NameCounts {
std::unordered_map<string, int> counts;
};
-string MakeUniquePath(string name) {
+string MakeUniqueFilename(string name) {
static NameCounts& instance = *new NameCounts;
// Remove illegal characters from `name`.
@@ -50,26 +50,41 @@ string MakeUniquePath(string name) {
count = instance.counts[name]++;
}
- legacy_flags::DumpGraphFlags* flags = legacy_flags::GetDumpGraphFlags();
- string path = strings::StrCat(flags->tf_dump_graph_prefix, "/", name);
+ string filename = name;
if (count > 0) {
- strings::StrAppend(&path, "_", count);
+ strings::StrAppend(&filename, "_", count);
}
- strings::StrAppend(&path, ".pbtxt");
- return path;
+ strings::StrAppend(&filename, ".pbtxt");
+ return filename;
+}
+
+string WriteTextProtoToUniqueFile(
+ Env* env, const string& name, const char* proto_type,
+ const ::tensorflow::protobuf::Message& proto) {
+ const string& dirname =
+ legacy_flags::GetDumpGraphFlags()->tf_dump_graph_prefix;
+ Status status = env->RecursivelyCreateDir(dirname);
+ if (!status.ok()) {
+ LOG(WARNING) << "Failed to create " << dirname << " for dumping "
+ << proto_type << ": " << status;
+ return "(unavailable)";
+ }
+ string filepath = strings::StrCat(dirname, "/", MakeUniqueFilename(name));
+ status = WriteTextProto(Env::Default(), filepath, proto);
+ if (!status.ok()) {
+ LOG(WARNING) << "Failed to dump " << proto_type << " to file: " << filepath
+ << " : " << status;
+ return "(unavailable)";
+ }
+ LOG(INFO) << "Dumped " << proto_type << " to " << filepath;
+ return filepath;
}
} // anonymous namespace
string DumpGraphDefToFile(const string& name, GraphDef const& graph_def) {
- string path = MakeUniquePath(name);
- Status status = WriteTextProto(Env::Default(), path, graph_def);
- if (!status.ok()) {
- VLOG(1) << "Failed to dump GraphDef to file: " << path << " : " << status;
- path.clear();
- path = "(unavailable)";
- }
- return path;
+ return WriteTextProtoToUniqueFile(Env::Default(), name, "GraphDef",
+ graph_def);
}
string DumpGraphToFile(const string& name, Graph const& graph,
@@ -83,15 +98,7 @@ string DumpGraphToFile(const string& name, Graph const& graph,
}
string DumpFunctionDefToFile(const string& name, FunctionDef const& fdef) {
- string path = MakeUniquePath(name);
- Status status = WriteTextProto(Env::Default(), path, fdef);
- if (!status.ok()) {
- VLOG(1) << "Failed to dump FunctionDef to file: " << path << " : "
- << status;
- path.clear();
- path = "(unavailable)";
- }
- return path;
+ return WriteTextProtoToUniqueFile(Env::Default(), name, "FunctionDef", fdef);
}
} // namespace dump_graph
diff --git a/tensorflow/compiler/tf2xla/functionalize_control_flow.cc b/tensorflow/compiler/tf2xla/functionalize_control_flow.cc
index 6cc95149a1..0904778f97 100644
--- a/tensorflow/compiler/tf2xla/functionalize_control_flow.cc
+++ b/tensorflow/compiler/tf2xla/functionalize_control_flow.cc
@@ -177,8 +177,8 @@ Status CheckNoCycleContains(const Node* node, const int num_nodes) {
visited[current_node->id()] = true;
for (const Edge* out : current_node->out_edges()) {
if (out->dst() == node) {
- return errors::Internal("Detect a cycle: Node \"", node->name(), "\"(",
- node->def().op(), ") feeds into itself.");
+ return errors::Internal("Detected a cycle: ", FormatNodeForError(*node),
+ "(", node->def().op(), ") feeds into itself.");
} else if (!visited[out->dst()->id()]) {
ready.push_back(out->dst());
}
@@ -324,7 +324,7 @@ Status AddMissingFunctionDef(const FunctionDef& fdef,
if (library->Find(node.op())) {
continue;
}
- // The function refered by 'SymbolicGradient' node is specified in its
+ // The function referred by 'SymbolicGradient' node is specified in its
// attribute 'f'.
if (node.op() == FunctionLibraryDefinition::kGradientOp) {
const AttrValue* attr =
@@ -437,22 +437,24 @@ Status FunctionalizeLoop(const FunctionLibraryDefinition* lookup_library,
continue;
}
if (enter_merge != nullptr) {
- return errors::Internal(
- "Enter node for loop-varying argument ", arg.enter->name(),
- " has multiple successors: ", enter_merge->dst()->name(), " and ",
- e->dst()->name());
+ return errors::Internal("Enter node for loop-varying argument ",
+ FormatNodeForError(*arg.enter),
+ " has multiple successors: ",
+ FormatNodeForError(*enter_merge->dst()),
+ " and ", FormatNodeForError(*e->dst()));
}
enter_merge = e;
}
if (enter_merge == nullptr) {
return errors::Internal("Enter node for loop-varying argument ",
- arg.enter->name(), " has zero successors");
+ FormatNodeForError(*arg.enter),
+ " has zero successors");
}
arg.merge = enter_merge->dst();
if (!IsMerge(arg.merge)) {
return errors::InvalidArgument(
"Successor of Enter node for loop-varying argument ",
- arg.merge->name(),
+ FormatNodeForError(*arg.merge),
" is not a Merge node; got: ", arg.merge->type_string());
}
@@ -462,7 +464,7 @@ Status FunctionalizeLoop(const FunctionLibraryDefinition* lookup_library,
return errors::InvalidArgument(
"Unexpected number of inputs to Merge node for loop-varying "
"argument ",
- arg.merge->name(), "; expected 2, got ",
+ FormatNodeForError(*arg.merge), "; expected 2, got ",
arg.merge->input_types().size());
}
TF_RETURN_IF_ERROR(arg.merge->input_node(1 - enter_merge->dst_input(),
@@ -470,7 +472,7 @@ Status FunctionalizeLoop(const FunctionLibraryDefinition* lookup_library,
if (!IsNextIteration(arg.next_iteration)) {
return errors::InvalidArgument(
"Expected NextIteration node as input to Merge node; got node ",
- arg.next_iteration->name(), " with kind ",
+ FormatNodeForError(*arg.next_iteration), " with kind ",
arg.next_iteration->type_string());
}
@@ -481,14 +483,14 @@ Status FunctionalizeLoop(const FunctionLibraryDefinition* lookup_library,
switches.find(edge->dst()) != switches.end()) {
if (arg.switch_node != nullptr) {
return errors::InvalidArgument("Duplicate Switch successors to ",
- arg.merge->name());
+ FormatNodeForError(*arg.merge));
}
arg.switch_node = edge->dst();
}
}
if (arg.switch_node == nullptr) {
return errors::InvalidArgument("Missing Switch successor to ",
- arg.merge->name());
+ FormatNodeForError(*arg.merge));
}
// Update the device on the Identity outputs of the switch to match their
@@ -516,14 +518,15 @@ Status FunctionalizeLoop(const FunctionLibraryDefinition* lookup_library,
possible_exit.pop_front();
if (IsExit(edge->dst())) {
if (arg.exit != nullptr) {
- return errors::InvalidArgument("Duplicate Exit successors to ",
- arg.switch_node->name());
+ return errors::InvalidArgument(
+ "Duplicate Exit successors to ",
+ FormatNodeForError(*arg.switch_node));
}
arg.exit = edge->dst();
} else {
if (!IsIdentity(edge->dst())) {
return errors::Unimplemented("General graph between switch (",
- arg.switch_node->name(),
+ FormatNodeForError(*arg.switch_node),
") and exit node of frame ",
frame->name, " not supported yet.");
}
@@ -1470,7 +1473,7 @@ Status FunctionalizeControlFlow(const FunctionLibraryDefinition* lookup_library,
if (!unreachable_nodes.empty()) {
return errors::InvalidArgument(
"The following nodes are unreachable from the source in the graph: ",
- tensorflow::str_util::Join(unreachable_nodes, ", "));
+ errors::FormatNodeNamesForError(unreachable_nodes));
}
// Builds Frames, indexed by name.
diff --git a/tensorflow/compiler/tf2xla/functionalize_control_flow_test.cc b/tensorflow/compiler/tf2xla/functionalize_control_flow_test.cc
index aae2f8ee5a..ccf249b35d 100644
--- a/tensorflow/compiler/tf2xla/functionalize_control_flow_test.cc
+++ b/tensorflow/compiler/tf2xla/functionalize_control_flow_test.cc
@@ -1064,7 +1064,10 @@ TEST(FunctionalizeControlFlow, Cycle) {
// less -> XlaIf <--> identity.
Status status = FunctionalizeControlFlow(graph.get(), &library);
EXPECT_FALSE(status.ok());
- EXPECT_TRUE(str_util::StrContains(status.error_message(), "Detect a cycle"))
+ EXPECT_TRUE(str_util::StrContains(status.error_message(), "Detected a cycle"))
+ << status.error_message();
+ EXPECT_TRUE(
+ str_util::StrContains(status.error_message(), "{{node cond/Less_5_If}}"))
<< status.error_message();
}
diff --git a/tensorflow/compiler/tf2xla/graph_compiler.cc b/tensorflow/compiler/tf2xla/graph_compiler.cc
index e1cea03865..e4fdf0a618 100644
--- a/tensorflow/compiler/tf2xla/graph_compiler.cc
+++ b/tensorflow/compiler/tf2xla/graph_compiler.cc
@@ -29,7 +29,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_context.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/xla/client/client_library.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/common_runtime/device.h"
#include "tensorflow/core/common_runtime/executor.h"
#include "tensorflow/core/common_runtime/function.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/BUILD b/tensorflow/compiler/tf2xla/kernels/BUILD
index d88a34dfd9..0609e22338 100644
--- a/tensorflow/compiler/tf2xla/kernels/BUILD
+++ b/tensorflow/compiler/tf2xla/kernels/BUILD
@@ -123,12 +123,14 @@ tf_kernel_library(
"//tensorflow/compiler/xla:util",
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:client_library",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:arithmetic",
"//tensorflow/compiler/xla/client/lib:constants",
"//tensorflow/compiler/xla/client/lib:math",
"//tensorflow/compiler/xla/client/lib:numeric",
"//tensorflow/compiler/xla/client/lib:prng",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client/lib:sorting",
"//tensorflow/core:framework",
"//tensorflow/core:image_ops_op_lib",
"//tensorflow/core:lib",
@@ -164,7 +166,8 @@ tf_kernel_library(
"//tensorflow/compiler/tf2xla:xla_compiler",
"//tensorflow/compiler/tf2xla/ops:xla_ops",
"//tensorflow/compiler/xla:literal",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/core:framework",
"//tensorflow/core:lib",
"//tensorflow/core:protos_all_cc",
@@ -180,7 +183,7 @@ tf_kernel_library(
"//tensorflow/compiler/tf2xla:xla_compiler",
"//tensorflow/compiler/tf2xla/ops:xla_ops",
"//tensorflow/compiler/xla:literal",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/core:framework",
"//tensorflow/core:lib",
"//tensorflow/core:protos_all_cc",
@@ -217,8 +220,8 @@ tf_kernel_library(
"//tensorflow/compiler/xla:literal",
"//tensorflow/compiler/xla:literal_util",
"//tensorflow/compiler/xla/client:client_library",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/client/lib:arithmetic",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
"//tensorflow/core:framework",
"//tensorflow/core:lib",
"//tensorflow/core/kernels:argmax_op",
diff --git a/tensorflow/compiler/tf2xla/kernels/aggregate_ops.cc b/tensorflow/compiler/tf2xla/kernels/aggregate_ops.cc
index e335328280..41a453da80 100644
--- a/tensorflow/compiler/tf2xla/kernels/aggregate_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/aggregate_ops.cc
@@ -15,7 +15,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
namespace tensorflow {
namespace {
diff --git a/tensorflow/compiler/tf2xla/kernels/batch_norm_op.cc b/tensorflow/compiler/tf2xla/kernels/batch_norm_op.cc
index c4af79281d..b3ad0aea84 100644
--- a/tensorflow/compiler/tf2xla/kernels/batch_norm_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/batch_norm_op.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/util/tensor_format.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/kernels/batchtospace_op.cc b/tensorflow/compiler/tf2xla/kernels/batchtospace_op.cc
index 26130fd9e7..48f2a005ab 100644
--- a/tensorflow/compiler/tf2xla/kernels/batchtospace_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/batchtospace_op.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
namespace tensorflow {
namespace {
diff --git a/tensorflow/compiler/tf2xla/kernels/bias_ops.cc b/tensorflow/compiler/tf2xla/kernels/bias_ops.cc
index e9b2c0b16d..41f540506b 100644
--- a/tensorflow/compiler/tf2xla/kernels/bias_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/bias_ops.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/framework/tensor_shape.h"
#include "tensorflow/core/util/tensor_format.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/binary_ops.cc b/tensorflow/compiler/tf2xla/kernels/binary_ops.cc
index d6d4ae8937..2c328102e0 100644
--- a/tensorflow/compiler/tf2xla/kernels/binary_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/binary_ops.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/client_library.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/types.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/bucketize_op.cc b/tensorflow/compiler/tf2xla/kernels/bucketize_op.cc
index efbdb76eaa..5078f8662b 100644
--- a/tensorflow/compiler/tf2xla/kernels/bucketize_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/bucketize_op.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/op_kernel.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/kernels/cast_op.cc b/tensorflow/compiler/tf2xla/kernels/cast_op.cc
index 62eebf762b..8cc2479dd5 100644
--- a/tensorflow/compiler/tf2xla/kernels/cast_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/cast_op.cc
@@ -17,7 +17,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/primitive_util.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/categorical_op.cc b/tensorflow/compiler/tf2xla/kernels/categorical_op.cc
index 1784e712b5..e7fef77edc 100644
--- a/tensorflow/compiler/tf2xla/kernels/categorical_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/categorical_op.cc
@@ -21,7 +21,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/framework/tensor_shape.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/clip_by_value_op.cc b/tensorflow/compiler/tf2xla/kernels/clip_by_value_op.cc
index 4e6d33304c..547fe48046 100644
--- a/tensorflow/compiler/tf2xla/kernels/clip_by_value_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/clip_by_value_op.cc
@@ -15,7 +15,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/tensor_shape.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/kernels/concat_op.cc b/tensorflow/compiler/tf2xla/kernels/concat_op.cc
index e3a32a5c0e..f410605104 100644
--- a/tensorflow/compiler/tf2xla/kernels/concat_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/concat_op.cc
@@ -22,7 +22,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/register_types.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/const_op.cc b/tensorflow/compiler/tf2xla/kernels/const_op.cc
index f4360d8c3f..da8cf3fc6f 100644
--- a/tensorflow/compiler/tf2xla/kernels/const_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/const_op.cc
@@ -17,7 +17,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_compiler.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/framework/tensor.pb.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/conv_ops.cc b/tensorflow/compiler/tf2xla/kernels/conv_ops.cc
index 48ac4867ed..5da7972397 100644
--- a/tensorflow/compiler/tf2xla/kernels/conv_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/conv_ops.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/lib/numeric.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/core/framework/numeric_op.h"
#include "tensorflow/core/framework/op_kernel.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/cross_op.cc b/tensorflow/compiler/tf2xla/kernels/cross_op.cc
index 500a564f3f..db579a5b35 100644
--- a/tensorflow/compiler/tf2xla/kernels/cross_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/cross_op.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
namespace tensorflow {
namespace {
diff --git a/tensorflow/compiler/tf2xla/kernels/cwise_ops.cc b/tensorflow/compiler/tf2xla/kernels/cwise_ops.cc
index 9ff3e02228..ef1015552d 100644
--- a/tensorflow/compiler/tf2xla/kernels/cwise_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/cwise_ops.cc
@@ -22,7 +22,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/client_library.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/types.h"
#include "tensorflow/core/util/bcast.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/cwise_ops.h b/tensorflow/compiler/tf2xla/kernels/cwise_ops.h
index 4f92dbc874..a5b870f8db 100644
--- a/tensorflow/compiler/tf2xla/kernels/cwise_ops.h
+++ b/tensorflow/compiler/tf2xla/kernels/cwise_ops.h
@@ -20,7 +20,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/xla/client/client_library.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/util/bcast.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/depthtospace_op.cc b/tensorflow/compiler/tf2xla/kernels/depthtospace_op.cc
index f314920025..12b0e38288 100644
--- a/tensorflow/compiler/tf2xla/kernels/depthtospace_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/depthtospace_op.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/util/tensor_format.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/kernels/diag_op.cc b/tensorflow/compiler/tf2xla/kernels/diag_op.cc
index 22cda27567..ed44ad218b 100644
--- a/tensorflow/compiler/tf2xla/kernels/diag_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/diag_op.cc
@@ -20,7 +20,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/lib/constants.h"
#include "tensorflow/compiler/xla/client/lib/numeric.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/util.h"
#include "tensorflow/core/framework/op_kernel.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/dynamic_slice_ops.cc b/tensorflow/compiler/tf2xla/kernels/dynamic_slice_ops.cc
index 3b86ea34c9..a3389d5b90 100644
--- a/tensorflow/compiler/tf2xla/kernels/dynamic_slice_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/dynamic_slice_ops.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/shape_util.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/compiler/tf2xla/type_util.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/dynamic_stitch_op.cc b/tensorflow/compiler/tf2xla/kernels/dynamic_stitch_op.cc
index 958231505b..cb73053666 100644
--- a/tensorflow/compiler/tf2xla/kernels/dynamic_stitch_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/dynamic_stitch_op.cc
@@ -20,7 +20,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/register_types.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/elu_op.cc b/tensorflow/compiler/tf2xla/kernels/elu_op.cc
index 81f42e504e..5fdb1d972c 100644
--- a/tensorflow/compiler/tf2xla/kernels/elu_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/elu_op.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/kernels/cwise_ops.h"
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/framework/types.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/extract_image_patches_op.cc b/tensorflow/compiler/tf2xla/kernels/extract_image_patches_op.cc
index 65d42a302f..c68b0bfd79 100644
--- a/tensorflow/compiler/tf2xla/kernels/extract_image_patches_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/extract_image_patches_op.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/lib/numeric.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/util/tensor_format.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/kernels/fake_quantize_ops.cc b/tensorflow/compiler/tf2xla/kernels/fake_quantize_ops.cc
index 2fd1a34741..cdba6680de 100644
--- a/tensorflow/compiler/tf2xla/kernels/fake_quantize_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/fake_quantize_ops.cc
@@ -17,7 +17,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/platform/macros.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/kernels/fft_ops.cc b/tensorflow/compiler/tf2xla/kernels/fft_ops.cc
index b2b00e51e3..80bcef9663 100644
--- a/tensorflow/compiler/tf2xla/kernels/fft_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/fft_ops.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/core/framework/numeric_op.h"
#include "tensorflow/core/framework/op_kernel.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/fill_op.cc b/tensorflow/compiler/tf2xla/kernels/fill_op.cc
index 95faa1d058..54b21a2782 100644
--- a/tensorflow/compiler/tf2xla/kernels/fill_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/fill_op.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/framework/register_types.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/gather_op.cc b/tensorflow/compiler/tf2xla/kernels/gather_op.cc
index 5f041be5df..35de96e0aa 100644
--- a/tensorflow/compiler/tf2xla/kernels/gather_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/gather_op.cc
@@ -21,7 +21,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/framework/op_kernel.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/gather_op_helpers.h b/tensorflow/compiler/tf2xla/kernels/gather_op_helpers.h
index d898e43b85..92346283c3 100644
--- a/tensorflow/compiler/tf2xla/kernels/gather_op_helpers.h
+++ b/tensorflow/compiler/tf2xla/kernels/gather_op_helpers.h
@@ -20,7 +20,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/xla/client/client_library.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/util/bcast.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/if_op.cc b/tensorflow/compiler/tf2xla/kernels/if_op.cc
index e2160feba0..ceb2af756c 100644
--- a/tensorflow/compiler/tf2xla/kernels/if_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/if_op.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_context.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/kernels/image_ops.cc b/tensorflow/compiler/tf2xla/kernels/image_ops.cc
index cb4caf7bcb..33a73fe5fd 100644
--- a/tensorflow/compiler/tf2xla/kernels/image_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/image_ops.cc
@@ -17,7 +17,12 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
+#include "tensorflow/compiler/xla/client/lib/constants.h"
+#include "tensorflow/compiler/xla/client/lib/sorting.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/shape_util.h"
+#include "tensorflow/core/framework/tensor_shape.h"
namespace tensorflow {
namespace {
@@ -311,5 +316,150 @@ class AdjustHueOp : public XlaOpKernel {
};
REGISTER_XLA_OP(Name("AdjustHue"), AdjustHueOp);
+class NonMaxSuppressionOp : public XlaOpKernel {
+ public:
+ explicit NonMaxSuppressionOp(OpKernelConstruction* context)
+ : XlaOpKernel(context) {
+ OP_REQUIRES_OK(context, context->GetAttr("pad_to_max_output_size",
+ &pad_to_max_output_size_));
+ }
+
+ void Compile(XlaOpKernelContext* context) override {
+ // TODO(b/111646731): Improve scalability of this op, using blocking.
+ int num_boxes_dim = 0;
+ int coords_dim = 1;
+ const TensorShape& boxes_shape = context->InputShape("boxes");
+ OP_REQUIRES(context, TensorShapeUtils::IsMatrix(boxes_shape),
+ errors::InvalidArgument("boxes must be 2-D, currently: ",
+ boxes_shape.DebugString()));
+ const int64 num_boxes = boxes_shape.dim_size(num_boxes_dim);
+ OP_REQUIRES(context, boxes_shape.dim_size(coords_dim) == 4,
+ errors::InvalidArgument("boxes must have 4 columns",
+ boxes_shape.DebugString()));
+ const TensorShape& scores_shape = context->InputShape("scores");
+ OP_REQUIRES(context, TensorShapeUtils::IsVector(scores_shape),
+ errors::InvalidArgument("scores must be 1-D, currently: ",
+ scores_shape.DebugString()));
+ OP_REQUIRES(
+ context, scores_shape.dim_size(0) == num_boxes,
+ errors::InvalidArgument("scores size must equal number of boxes",
+ scores_shape.DebugString()));
+ OP_REQUIRES(context, pad_to_max_output_size_,
+ errors::InvalidArgument(
+ "XLA compilation requires pad_to_max_output_size == True"));
+
+ xla::XlaOp boxes = context->Input("boxes");
+ xla::XlaOp scores = context->Input("scores");
+ int64 output_size;
+ OP_REQUIRES_OK(context, context->ConstantInputAsIntScalar(2, &output_size));
+ OP_REQUIRES(
+ context, output_size >= 0,
+ errors::InvalidArgument("Need output_size >= 0, got ", output_size));
+ xla::XlaOp score_thresh = context->Input("score_threshold");
+ xla::XlaOp iou_thresh = context->Input("iou_threshold");
+
+ xla::XlaBuilder* const builder = context->builder();
+
+ // Choose a more convenient layout.
+ xla::XlaOp boxes_t = xla::Transpose(boxes, {1, 0});
+ coords_dim = 0;
+ num_boxes_dim = 1;
+
+ // Shapes are henceforth [1, num_boxes].
+ xla::XlaOp coord_y0 = xla::SliceInDim(boxes_t,
+ /*start_index=*/0,
+ /*limit_index=*/1,
+ /*stride=*/1,
+ /*dimno=*/coords_dim);
+ xla::XlaOp coord_x0 = xla::SliceInDim(boxes_t,
+ /*start_index=*/1,
+ /*limit_index=*/2,
+ /*stride=*/1,
+ /*dimno=*/coords_dim);
+ xla::XlaOp coord_y1 = xla::SliceInDim(boxes_t,
+ /*start_index=*/2,
+ /*limit_index=*/3,
+ /*stride=*/1,
+ /*dimno=*/coords_dim);
+ xla::XlaOp coord_x1 = xla::SliceInDim(boxes_t,
+ /*start_index=*/3,
+ /*limit_index=*/4,
+ /*stride=*/1,
+ /*dimno=*/coords_dim);
+ xla::XlaOp y1 =
+ xla::Select(xla::Le(coord_y0, coord_y1), coord_y0, coord_y1);
+ xla::XlaOp y2 =
+ xla::Select(xla::Le(coord_y0, coord_y1), coord_y1, coord_y0);
+ xla::XlaOp x1 =
+ xla::Select(xla::Le(coord_x0, coord_x1), coord_x0, coord_x1);
+ xla::XlaOp x2 =
+ xla::Select(xla::Le(coord_x0, coord_x1), coord_x1, coord_x0);
+ xla::XlaOp area = (y2 - y1) * (x2 - x1);
+
+ // Transpose the 1xN tensors, instead of the NxN tensors.
+ xla::XlaOp y1_t = xla::Transpose(y1, {1, 0});
+ xla::XlaOp y2_t = xla::Transpose(y2, {1, 0});
+ xla::XlaOp x1_t = xla::Transpose(x1, {1, 0});
+ xla::XlaOp x2_t = xla::Transpose(x2, {1, 0});
+ xla::XlaOp area_t = xla::Transpose(area, {1, 0});
+
+ // Shapes are henceforth [num_boxes, num_boxes].
+ xla::XlaOp i_xmin = xla::Max(x1, x1_t);
+ xla::XlaOp i_ymin = xla::Max(y1, y1_t);
+ xla::XlaOp i_xmax = xla::Min(x2, x2_t);
+ xla::XlaOp i_ymax = xla::Min(y2, y2_t);
+ auto square_zero = xla::ZerosLike(i_xmin);
+
+ xla::XlaOp i_area = xla::Max(i_xmax - i_xmin, square_zero) *
+ xla::Max(i_ymax - i_ymin, square_zero);
+ xla::XlaOp u_area = area + area_t - i_area;
+ xla::XlaOp iou = i_area / u_area;
+
+ xla::XlaOp iou_thresh_mask = xla::Gt(iou, iou_thresh + square_zero);
+ xla::XlaOp scores_2d = xla::Reshape(scores, {num_boxes, 1});
+ xla::XlaOp score_cmp_mask =
+ xla::Gt(scores_2d, xla::Transpose(scores_2d, {1, 0}));
+ xla::XlaOp suppress = xla::And(iou_thresh_mask, score_cmp_mask);
+
+ // Shapes are [num_boxes] after the reduce.
+ xla::XlaOp included_iou = xla::Not(xla::Reduce(
+ suppress,
+ /*init_value=*/xla::ConstantR0<bool>(builder, false),
+ /*computation=*/CreateScalarOrComputation(xla::PRED, builder),
+ /*dimensions_to_reduce=*/{0}));
+ xla::XlaOp included_score =
+ xla::Gt(scores, xla::Broadcast(score_thresh, {num_boxes}));
+ xla::XlaOp included = xla::And(included_iou, included_score);
+ xla::XlaOp neg_inf =
+ xla::Broadcast(xla::MinValue(builder, xla::F32), {num_boxes});
+ xla::XlaOp scores_included = xla::Select(included, scores, neg_inf);
+
+ xla::XlaOp ones_included = xla::Select(
+ included,
+ xla::Broadcast(xla::ConstantR0<int32>(builder, 1), {num_boxes}),
+ xla::Broadcast(xla::ConstantR0<int32>(builder, 0), {num_boxes}));
+
+ // num_valid is scalar.
+ xla::XlaOp num_valid = xla::Reduce(
+ ones_included,
+ /*init_value=*/xla::ConstantR0<int>(builder, 0),
+ /*computation=*/CreateScalarAddComputation(xla::S32, builder),
+ /*dimensions_to_reduce=*/{0});
+
+ xla::XlaOp output_tuple = TopK(scores_included, output_size);
+ xla::XlaOp selected_indices = xla::GetTupleElement(output_tuple, 1);
+
+ context->SetOutput(0, selected_indices);
+ context->SetOutput(1, num_valid);
+ }
+
+ private:
+ bool pad_to_max_output_size_;
+};
+
+REGISTER_XLA_OP(
+ Name("NonMaxSuppressionV4").CompileTimeConstInput("max_output_size"),
+ NonMaxSuppressionOp);
+
} // namespace
} // namespace tensorflow
diff --git a/tensorflow/compiler/tf2xla/kernels/image_resize_ops.cc b/tensorflow/compiler/tf2xla/kernels/image_resize_ops.cc
index d6bf92fb3d..8d75624e74 100644
--- a/tensorflow/compiler/tf2xla/kernels/image_resize_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/image_resize_ops.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/array4d.h"
#include "tensorflow/compiler/xla/client/lib/numeric.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/framework/register_types.h"
#include "tensorflow/core/lib/math/math_util.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/l2loss_op.cc b/tensorflow/compiler/tf2xla/kernels/l2loss_op.cc
index 9e64711051..f028e361bc 100644
--- a/tensorflow/compiler/tf2xla/kernels/l2loss_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/l2loss_op.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/types.h"
#include "tensorflow/core/kernels/no_op.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/listdiff_op.cc b/tensorflow/compiler/tf2xla/kernels/listdiff_op.cc
index 2fb072f827..a11bbe918f 100644
--- a/tensorflow/compiler/tf2xla/kernels/listdiff_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/listdiff_op.cc
@@ -22,7 +22,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/framework/register_types.h"
#include "tensorflow/core/lib/core/errors.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/lrn_ops.cc b/tensorflow/compiler/tf2xla/kernels/lrn_ops.cc
index dc934543cb..87ee2d3aed 100644
--- a/tensorflow/compiler/tf2xla/kernels/lrn_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/lrn_ops.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/kernels/matmul_op.cc b/tensorflow/compiler/tf2xla/kernels/matmul_op.cc
index aa45b02551..6440770c29 100644
--- a/tensorflow/compiler/tf2xla/kernels/matmul_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/matmul_op.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/op_kernel.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/kernels/matrix_band_part_op.cc b/tensorflow/compiler/tf2xla/kernels/matrix_band_part_op.cc
index e06c87db7a..8dfd7de591 100644
--- a/tensorflow/compiler/tf2xla/kernels/matrix_band_part_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/matrix_band_part_op.cc
@@ -17,7 +17,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/lib/numeric.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/tensor_shape.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/kernels/matrix_set_diag_op.cc b/tensorflow/compiler/tf2xla/kernels/matrix_set_diag_op.cc
index e2ab4b83cf..c0ca881ff8 100644
--- a/tensorflow/compiler/tf2xla/kernels/matrix_set_diag_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/matrix_set_diag_op.cc
@@ -17,7 +17,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/lib/numeric.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/kernels/mirror_pad_op.cc b/tensorflow/compiler/tf2xla/kernels/mirror_pad_op.cc
index 529959dbd9..eedfc3c914 100644
--- a/tensorflow/compiler/tf2xla/kernels/mirror_pad_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/mirror_pad_op.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/util/mirror_pad_mode.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/kernels/pack_op.cc b/tensorflow/compiler/tf2xla/kernels/pack_op.cc
index 3aed47de26..a9b519d892 100644
--- a/tensorflow/compiler/tf2xla/kernels/pack_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/pack_op.cc
@@ -22,7 +22,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/register_types.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/pad_op.cc b/tensorflow/compiler/tf2xla/kernels/pad_op.cc
index 89fd610bc6..e5937b56c1 100644
--- a/tensorflow/compiler/tf2xla/kernels/pad_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/pad_op.cc
@@ -17,7 +17,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/framework/register_types.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/pooling_ops.cc b/tensorflow/compiler/tf2xla/kernels/pooling_ops.cc
index 12d9cb9bac..3d506e71e0 100644
--- a/tensorflow/compiler/tf2xla/kernels/pooling_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/pooling_ops.cc
@@ -21,7 +21,8 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/lib/constants.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/util.h"
#include "tensorflow/core/framework/op_kernel.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/quantize_and_dequantize_op.cc b/tensorflow/compiler/tf2xla/kernels/quantize_and_dequantize_op.cc
index e88221e4f4..6f4ed496a1 100644
--- a/tensorflow/compiler/tf2xla/kernels/quantize_and_dequantize_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/quantize_and_dequantize_op.cc
@@ -19,7 +19,8 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/lib/constants.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/core/platform/macros.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/kernels/random_ops.cc b/tensorflow/compiler/tf2xla/kernels/random_ops.cc
index 607cad798a..2da9340625 100644
--- a/tensorflow/compiler/tf2xla/kernels/random_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/random_ops.cc
@@ -27,7 +27,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/lib/numeric.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/framework/tensor_shape.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/reduce_window_op.cc b/tensorflow/compiler/tf2xla/kernels/reduce_window_op.cc
index 76bd1e62aa..b11a4ce36d 100644
--- a/tensorflow/compiler/tf2xla/kernels/reduce_window_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/reduce_window_op.cc
@@ -19,7 +19,8 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_compiler.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/core/framework/function.h"
#include "tensorflow/core/framework/op_kernel.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/reduction_ops.cc b/tensorflow/compiler/tf2xla/kernels/reduction_ops.cc
index be7f2bce8c..0d260fa8fc 100644
--- a/tensorflow/compiler/tf2xla/kernels/reduction_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/reduction_ops.cc
@@ -20,7 +20,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/lib/constants.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/reduction_ops.h b/tensorflow/compiler/tf2xla/kernels/reduction_ops.h
index 8333f9b288..466e79828d 100644
--- a/tensorflow/compiler/tf2xla/kernels/reduction_ops.h
+++ b/tensorflow/compiler/tf2xla/kernels/reduction_ops.h
@@ -19,7 +19,7 @@ limitations under the License.
#define TENSORFLOW_COMPILER_TF2XLA_KERNELS_REDUCTION_OPS_H_
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/op_kernel.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/kernels/reduction_ops_common.cc b/tensorflow/compiler/tf2xla/kernels/reduction_ops_common.cc
index ed1d1c6610..b52f0a0ab6 100644
--- a/tensorflow/compiler/tf2xla/kernels/reduction_ops_common.cc
+++ b/tensorflow/compiler/tf2xla/kernels/reduction_ops_common.cc
@@ -19,7 +19,8 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/type_util.h"
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/relu_op.cc b/tensorflow/compiler/tf2xla/kernels/relu_op.cc
index f4b804e546..d35777ccb1 100644
--- a/tensorflow/compiler/tf2xla/kernels/relu_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/relu_op.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/kernels/cwise_ops.h"
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/framework/types.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/reshape_op.cc b/tensorflow/compiler/tf2xla/kernels/reshape_op.cc
index 354fec9be7..121750a82a 100644
--- a/tensorflow/compiler/tf2xla/kernels/reshape_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/reshape_op.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/register_types.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/retval_op.cc b/tensorflow/compiler/tf2xla/kernels/retval_op.cc
index 5be70a4ded..1911e6ea36 100644
--- a/tensorflow/compiler/tf2xla/kernels/retval_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/retval_op.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_context.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/status_macros.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/framework/op_kernel.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/reverse_op.cc b/tensorflow/compiler/tf2xla/kernels/reverse_op.cc
index ec15b4cc7a..d962ef4a5f 100644
--- a/tensorflow/compiler/tf2xla/kernels/reverse_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/reverse_op.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/register_types.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/reverse_sequence_op.cc b/tensorflow/compiler/tf2xla/kernels/reverse_sequence_op.cc
index c810456f94..03a50ef8a0 100644
--- a/tensorflow/compiler/tf2xla/kernels/reverse_sequence_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/reverse_sequence_op.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/lib/numeric.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/tensor_shape.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/kernels/scan_ops.cc b/tensorflow/compiler/tf2xla/kernels/scan_ops.cc
index 27ab3e1bf5..ab094d7dd1 100644
--- a/tensorflow/compiler/tf2xla/kernels/scan_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/scan_ops.cc
@@ -20,7 +20,8 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/partial_tensor_shape.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/scatter_nd_op.cc b/tensorflow/compiler/tf2xla/kernels/scatter_nd_op.cc
index 14709bb6cb..f1f32699fe 100644
--- a/tensorflow/compiler/tf2xla/kernels/scatter_nd_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/scatter_nd_op.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/status_macros.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/framework/op_kernel.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/segment_reduction_ops.cc b/tensorflow/compiler/tf2xla/kernels/segment_reduction_ops.cc
index e2ac7da2c2..b22ecb7c6d 100644
--- a/tensorflow/compiler/tf2xla/kernels/segment_reduction_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/segment_reduction_ops.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/lib/constants.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
namespace tensorflow {
namespace {
diff --git a/tensorflow/compiler/tf2xla/kernels/select_op.cc b/tensorflow/compiler/tf2xla/kernels/select_op.cc
index 5c010c9df2..6ce50efb4a 100644
--- a/tensorflow/compiler/tf2xla/kernels/select_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/select_op.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/kernels/bounds_check.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/sendrecv_ops.cc b/tensorflow/compiler/tf2xla/kernels/sendrecv_ops.cc
index 6281d6c653..a7f5a8f169 100644
--- a/tensorflow/compiler/tf2xla/kernels/sendrecv_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/sendrecv_ops.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/xla_data.pb.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/framework/types.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/shape_op.cc b/tensorflow/compiler/tf2xla/kernels/shape_op.cc
index 5798823cd5..4e0cf99d8e 100644
--- a/tensorflow/compiler/tf2xla/kernels/shape_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/shape_op.cc
@@ -20,7 +20,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/kernels/bounds_check.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/slice_op.cc b/tensorflow/compiler/tf2xla/kernels/slice_op.cc
index 1864584ade..6adc3c58de 100644
--- a/tensorflow/compiler/tf2xla/kernels/slice_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/slice_op.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/register_types.h"
#include "tensorflow/core/framework/tensor.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/softmax_op.cc b/tensorflow/compiler/tf2xla/kernels/softmax_op.cc
index a71fbcd901..1d7a63dc31 100644
--- a/tensorflow/compiler/tf2xla/kernels/softmax_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/softmax_op.cc
@@ -20,7 +20,8 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/lib/constants.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/framework/tensor_shape.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/sort_ops.cc b/tensorflow/compiler/tf2xla/kernels/sort_ops.cc
index faaf8964ff..aaeeae01cc 100644
--- a/tensorflow/compiler/tf2xla/kernels/sort_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/sort_ops.cc
@@ -15,7 +15,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
namespace tensorflow {
namespace {
diff --git a/tensorflow/compiler/tf2xla/kernels/spacetobatch_op.cc b/tensorflow/compiler/tf2xla/kernels/spacetobatch_op.cc
index 8a8525efa1..7327258c31 100644
--- a/tensorflow/compiler/tf2xla/kernels/spacetobatch_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/spacetobatch_op.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
namespace tensorflow {
namespace {
diff --git a/tensorflow/compiler/tf2xla/kernels/spacetodepth_op.cc b/tensorflow/compiler/tf2xla/kernels/spacetodepth_op.cc
index 47d282fe9e..4493539fe3 100644
--- a/tensorflow/compiler/tf2xla/kernels/spacetodepth_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/spacetodepth_op.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/util/tensor_format.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/kernels/split_op.cc b/tensorflow/compiler/tf2xla/kernels/split_op.cc
index 242638f981..93fc14e9ef 100644
--- a/tensorflow/compiler/tf2xla/kernels/split_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/split_op.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/register_types.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/stateless_random_ops.cc b/tensorflow/compiler/tf2xla/kernels/stateless_random_ops.cc
index cc4b13d3b9..5412e13547 100644
--- a/tensorflow/compiler/tf2xla/kernels/stateless_random_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/stateless_random_ops.cc
@@ -24,7 +24,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/lib/math.h"
#include "tensorflow/compiler/xla/client/lib/numeric.h"
#include "tensorflow/compiler/xla/client/lib/prng.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/framework/tensor_shape.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/strided_slice_op.cc b/tensorflow/compiler/tf2xla/kernels/strided_slice_op.cc
index c2165ccd86..1062399d91 100644
--- a/tensorflow/compiler/tf2xla/kernels/strided_slice_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/strided_slice_op.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/register_types.h"
#include "tensorflow/core/framework/tensor.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/tensor_array_ops.cc b/tensorflow/compiler/tf2xla/kernels/tensor_array_ops.cc
index 26326f18b8..be1814d8e3 100644
--- a/tensorflow/compiler/tf2xla/kernels/tensor_array_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/tensor_array_ops.cc
@@ -25,7 +25,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/tf2xla/xla_resource.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/partial_tensor_shape.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/tile_ops.cc b/tensorflow/compiler/tf2xla/kernels/tile_ops.cc
index c9e5694262..1233a37565 100644
--- a/tensorflow/compiler/tf2xla/kernels/tile_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/tile_ops.cc
@@ -20,7 +20,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/numeric_op.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/tensor.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/topk_op.cc b/tensorflow/compiler/tf2xla/kernels/topk_op.cc
index 82d4a69777..183879c760 100644
--- a/tensorflow/compiler/tf2xla/kernels/topk_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/topk_op.cc
@@ -13,11 +13,11 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
-#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/lib/numeric.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/lib/sorting.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/framework/types.h"
@@ -47,31 +47,12 @@ class TopKOp : public XlaOpKernel {
context, last_dim_size >= k,
errors::InvalidArgument("input must have at least k columns. Had ",
last_dim_size, ", needed ", k));
-
- xla::XlaBuilder* const b = context->builder();
if (last_dim_size < k) {
k = last_dim_size;
}
- const xla::XlaOp input = context->Input(0);
-
- xla::XlaOp iota_s32 = xla::Iota(b, xla::S32, last_dim_size);
- auto input_dims = input_shape.dim_sizes();
- std::vector<int64> broadcast_dims(input_dims.begin(), input_dims.end() - 1);
- xla::XlaOp broadcast_s32 = xla::Broadcast(iota_s32, broadcast_dims);
- xla::XlaOp sort_result = xla::Sort(xla::Neg(input), broadcast_s32);
-
- std::vector<int64> start_indices(input_shape.dims(), 0);
- std::vector<int64> limit_indices(input_dims.begin(), input_dims.end());
- limit_indices[last_dim] = k;
- std::vector<int64> strides(input_shape.dims(), 1);
-
- xla::XlaOp values =
- xla::Neg(xla::Slice(xla::GetTupleElement(sort_result, 0), start_indices,
- limit_indices, strides));
- xla::XlaOp indices = xla::Slice(xla::GetTupleElement(sort_result, 1),
- start_indices, limit_indices, strides);
- context->SetOutput(0, values);
- context->SetOutput(1, indices);
+ xla::XlaOp output_tuple = TopK(context->Input(0), k);
+ context->SetOutput(0, xla::GetTupleElement(output_tuple, 0));
+ context->SetOutput(1, xla::GetTupleElement(output_tuple, 1));
}
private:
diff --git a/tensorflow/compiler/tf2xla/kernels/training_ops.cc b/tensorflow/compiler/tf2xla/kernels/training_ops.cc
index 98df730249..be5e911386 100644
--- a/tensorflow/compiler/tf2xla/kernels/training_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/training_ops.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/lib/constants.h"
#include "tensorflow/compiler/xla/client/lib/math.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/framework/types.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/transpose_op.cc b/tensorflow/compiler/tf2xla/kernels/transpose_op.cc
index 6c721c48fe..f9148b3942 100644
--- a/tensorflow/compiler/tf2xla/kernels/transpose_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/transpose_op.cc
@@ -23,7 +23,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/framework/register_types.h"
#include "tensorflow/core/kernels/bounds_check.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/unary_ops.cc b/tensorflow/compiler/tf2xla/kernels/unary_ops.cc
index e6ec794cfd..0bdfc05726 100644
--- a/tensorflow/compiler/tf2xla/kernels/unary_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/unary_ops.cc
@@ -23,7 +23,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/lib/constants.h"
#include "tensorflow/compiler/xla/client/lib/math.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/kernels/unpack_op.cc b/tensorflow/compiler/tf2xla/kernels/unpack_op.cc
index f951127bb9..8671632976 100644
--- a/tensorflow/compiler/tf2xla/kernels/unpack_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/unpack_op.cc
@@ -22,7 +22,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/register_types.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/variable_ops.cc b/tensorflow/compiler/tf2xla/kernels/variable_ops.cc
index bb27b5d56f..2c92a585f5 100644
--- a/tensorflow/compiler/tf2xla/kernels/variable_ops.cc
+++ b/tensorflow/compiler/tf2xla/kernels/variable_ops.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/shape_util.h"
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/core/framework/kernel_def_builder.h"
#include "tensorflow/core/framework/types.h"
diff --git a/tensorflow/compiler/tf2xla/kernels/while_op.cc b/tensorflow/compiler/tf2xla/kernels/while_op.cc
index 009fdd81b2..1e8a376765 100644
--- a/tensorflow/compiler/tf2xla/kernels/while_op.cc
+++ b/tensorflow/compiler/tf2xla/kernels/while_op.cc
@@ -21,7 +21,8 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/core/framework/function.h"
#include "tensorflow/core/framework/op_kernel.h"
diff --git a/tensorflow/compiler/tf2xla/legacy_flags/backend_registration_flags.cc b/tensorflow/compiler/tf2xla/legacy_flags/backend_registration_flags.cc
deleted file mode 100644
index 661505021f..0000000000
--- a/tensorflow/compiler/tf2xla/legacy_flags/backend_registration_flags.cc
+++ /dev/null
@@ -1,63 +0,0 @@
-/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-==============================================================================*/
-
-// Legacy flags for the XLA bridge's backend registration modules.
-
-#include <mutex> // NOLINT
-#include <vector>
-
-#include "tensorflow/compiler/tf2xla/legacy_flags/backend_registration_flags.h"
-#include "tensorflow/compiler/xla/legacy_flags/parse_flags_from_env.h"
-#include "tensorflow/core/platform/types.h"
-#include "tensorflow/core/util/command_line_flags.h"
-
-namespace tensorflow {
-namespace legacy_flags {
-
-// Pointers to the parsed value of the flags and flag descriptors, initialized
-// via flags_init.
-static BackendRegistrationFlags* flags;
-static std::vector<Flag>* flag_list;
-static std::once_flag flags_init;
-
-// Allocate *flags. Called via call_once(&flags_init,...).
-static void AllocateFlags() {
- flags = new BackendRegistrationFlags;
- flags->tf_enable_prng_ops_gpu = false;
- flag_list = new std::vector<Flag>({
- Flag("tf_enable_prng_ops_gpu", &flags->tf_enable_prng_ops_gpu,
- "Whether to enable PRNG ops: [RandomStandardNormal | RandomUniform "
- "| RandomUniformInt | TruncatedNormal] on GPU."),
- });
- xla::legacy_flags::ParseFlagsFromEnv(*flag_list);
-}
-
-// Append to *append_to flag definitions associated with the XLA bridge's
-// backend registration modules.
-void AppendBackendRegistrationFlags(std::vector<Flag>* append_to) {
- std::call_once(flags_init, &AllocateFlags);
- append_to->insert(append_to->end(), flag_list->begin(), flag_list->end());
-}
-
-// Return a pointer to the BackendRegistrationFlags struct;
-// repeated calls return the same pointer.
-// This should be called only after Flags::Parse() has returned.
-BackendRegistrationFlags* GetBackendRegistrationFlags() {
- std::call_once(flags_init, &AllocateFlags);
- return flags;
-}
-
-} // namespace legacy_flags
-} // namespace tensorflow
diff --git a/tensorflow/compiler/tf2xla/legacy_flags/backend_registration_flags.h b/tensorflow/compiler/tf2xla/legacy_flags/backend_registration_flags.h
deleted file mode 100644
index 861c923dd5..0000000000
--- a/tensorflow/compiler/tf2xla/legacy_flags/backend_registration_flags.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-==============================================================================*/
-
-#ifndef TENSORFLOW_COMPILER_TF2XLA_LEGACY_FLAGS_BACKEND_REGISTRATION_FLAGS_H_
-#define TENSORFLOW_COMPILER_TF2XLA_LEGACY_FLAGS_BACKEND_REGISTRATION_FLAGS_H_
-
-// Legacy flags for the XLA bridge's backend registration modules.
-
-#include <vector>
-
-#include "tensorflow/core/platform/types.h"
-#include "tensorflow/core/util/command_line_flags.h"
-
-namespace tensorflow {
-namespace legacy_flags {
-
-// Append to *flag_list flag definitions associated with the XLA bridge's
-// backend registration modules.
-void AppendBackendRegistrationFlags(std::vector<tensorflow::Flag>* append_to);
-
-// The values of flags associated with the XLA bridge's backend registration
-// module.
-typedef struct {
- // Whether to enable RandomUniform op on GPU backend.
- // TODO (b/32333178): Remove this flag or set its default to true.
- bool tf_enable_prng_ops_gpu;
-} BackendRegistrationFlags;
-
-// Return a pointer to the BackendRegistrationFlags struct;
-// repeated calls return the same pointer.
-// This should be called only after Flags::Parse() has returned.
-BackendRegistrationFlags* GetBackendRegistrationFlags();
-
-} // namespace legacy_flags
-} // namespace tensorflow
-
-#endif // TENSORFLOW_COMPILER_TF2XLA_LEGACY_FLAGS_BACKEND_REGISTRATION_FLAGS_H_
diff --git a/tensorflow/compiler/tf2xla/lib/BUILD b/tensorflow/compiler/tf2xla/lib/BUILD
index 30039e256a..cb7a40e23d 100644
--- a/tensorflow/compiler/tf2xla/lib/BUILD
+++ b/tensorflow/compiler/tf2xla/lib/BUILD
@@ -25,8 +25,8 @@ cc_library(
"//tensorflow/compiler/xla:shape_util",
"//tensorflow/compiler/xla:status_macros",
"//tensorflow/compiler/xla:statusor",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/core:lib",
],
)
@@ -44,9 +44,9 @@ cc_library(
"//tensorflow/compiler/xla:shape_util",
"//tensorflow/compiler/xla:status_macros",
"//tensorflow/compiler/xla:statusor",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:constants",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/core:lib",
],
)
@@ -59,9 +59,9 @@ cc_library(
"//tensorflow/compiler/tf2xla:xla_compiler",
"//tensorflow/compiler/xla:status_macros",
"//tensorflow/compiler/xla:statusor",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/client/lib:constants",
"//tensorflow/compiler/xla/client/lib:math",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
"//tensorflow/core:protos_all_cc",
],
)
@@ -78,12 +78,12 @@ cc_library(
"//tensorflow/compiler/xla:shape_util",
"//tensorflow/compiler/xla:status_macros",
"//tensorflow/compiler/xla:statusor",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:arithmetic",
"//tensorflow/compiler/xla/client/lib:constants",
"//tensorflow/compiler/xla/client/lib:math",
"//tensorflow/compiler/xla/client/lib:numeric",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/core:lib",
],
)
@@ -100,9 +100,9 @@ cc_library(
"//tensorflow/compiler/xla:status_macros",
"//tensorflow/compiler/xla:statusor",
"//tensorflow/compiler/xla:util",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:arithmetic",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/core:lib",
],
)
@@ -119,10 +119,10 @@ cc_library(
"//tensorflow/compiler/xla:status_macros",
"//tensorflow/compiler/xla:statusor",
"//tensorflow/compiler/xla:util",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:constants",
"//tensorflow/compiler/xla/client/lib:numeric",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/core:lib",
],
)
@@ -142,7 +142,7 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -162,8 +162,8 @@ cc_library(
"//tensorflow/compiler/xla:status_macros",
"//tensorflow/compiler/xla:statusor",
"//tensorflow/compiler/xla:util",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/core:lib",
],
)
@@ -200,8 +200,8 @@ cc_library(
"//tensorflow/compiler/xla:shape_util",
"//tensorflow/compiler/xla:status_macros",
"//tensorflow/compiler/xla:statusor",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/core:lib",
],
)
diff --git a/tensorflow/compiler/tf2xla/lib/batch_dot.cc b/tensorflow/compiler/tf2xla/lib/batch_dot.cc
index 3c4eec081b..f666d22ea4 100644
--- a/tensorflow/compiler/tf2xla/lib/batch_dot.cc
+++ b/tensorflow/compiler/tf2xla/lib/batch_dot.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include <memory>
#include <vector>
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/status_macros.h"
#include "tensorflow/compiler/xla/statusor.h"
diff --git a/tensorflow/compiler/tf2xla/lib/batch_dot.h b/tensorflow/compiler/tf2xla/lib/batch_dot.h
index d07a9486f1..8757b16a1c 100644
--- a/tensorflow/compiler/tf2xla/lib/batch_dot.h
+++ b/tensorflow/compiler/tf2xla/lib/batch_dot.h
@@ -16,8 +16,8 @@ limitations under the License.
#ifndef TENSORFLOW_COMPILER_TF2XLA_LIB_BATCH_DOT_H_
#define TENSORFLOW_COMPILER_TF2XLA_LIB_BATCH_DOT_H_
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/lib/cholesky.cc b/tensorflow/compiler/tf2xla/lib/cholesky.cc
index 35b137aa2c..87d73eb3f0 100644
--- a/tensorflow/compiler/tf2xla/lib/cholesky.cc
+++ b/tensorflow/compiler/tf2xla/lib/cholesky.cc
@@ -23,7 +23,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/lib/util.h"
#include "tensorflow/compiler/tf2xla/lib/while_loop.h"
#include "tensorflow/compiler/xla/client/lib/constants.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/status_macros.h"
diff --git a/tensorflow/compiler/tf2xla/lib/cholesky.h b/tensorflow/compiler/tf2xla/lib/cholesky.h
index 0f6e0e9d15..1bef9bb166 100644
--- a/tensorflow/compiler/tf2xla/lib/cholesky.h
+++ b/tensorflow/compiler/tf2xla/lib/cholesky.h
@@ -16,8 +16,8 @@ limitations under the License.
#ifndef TENSORFLOW_COMPILER_TF2XLA_LIB_CHOLESKY_H_
#define TENSORFLOW_COMPILER_TF2XLA_LIB_CHOLESKY_H_
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/lib/qr.cc b/tensorflow/compiler/tf2xla/lib/qr.cc
index 9c8ac7af25..fc0c1ee838 100644
--- a/tensorflow/compiler/tf2xla/lib/qr.cc
+++ b/tensorflow/compiler/tf2xla/lib/qr.cc
@@ -25,7 +25,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/lib/constants.h"
#include "tensorflow/compiler/xla/client/lib/math.h"
#include "tensorflow/compiler/xla/client/lib/numeric.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/status_macros.h"
diff --git a/tensorflow/compiler/tf2xla/lib/qr.h b/tensorflow/compiler/tf2xla/lib/qr.h
index 3aa6a9b075..abd2316ac9 100644
--- a/tensorflow/compiler/tf2xla/lib/qr.h
+++ b/tensorflow/compiler/tf2xla/lib/qr.h
@@ -16,7 +16,7 @@ limitations under the License.
#ifndef TENSORFLOW_COMPILER_TF2XLA_LIB_QR_H_
#define TENSORFLOW_COMPILER_TF2XLA_LIB_QR_H_
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/lib/random.cc b/tensorflow/compiler/tf2xla/lib/random.cc
index 8ff10fbd3f..5e7cf00ee5 100644
--- a/tensorflow/compiler/tf2xla/lib/random.cc
+++ b/tensorflow/compiler/tf2xla/lib/random.cc
@@ -21,7 +21,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/xla/client/lib/constants.h"
#include "tensorflow/compiler/xla/client/lib/math.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/status_macros.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/lib/random.h b/tensorflow/compiler/tf2xla/lib/random.h
index 2c573fd85b..59fc5d0433 100644
--- a/tensorflow/compiler/tf2xla/lib/random.h
+++ b/tensorflow/compiler/tf2xla/lib/random.h
@@ -16,7 +16,7 @@ limitations under the License.
#ifndef TENSORFLOW_COMPILER_TF2XLA_LIB_RANDOM_H_
#define TENSORFLOW_COMPILER_TF2XLA_LIB_RANDOM_H_
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/core/framework/types.pb.h"
diff --git a/tensorflow/compiler/tf2xla/lib/scatter.cc b/tensorflow/compiler/tf2xla/lib/scatter.cc
index 739032fef7..ba22eff73a 100644
--- a/tensorflow/compiler/tf2xla/lib/scatter.cc
+++ b/tensorflow/compiler/tf2xla/lib/scatter.cc
@@ -21,7 +21,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/lib/util.h"
#include "tensorflow/compiler/tf2xla/lib/while_loop.h"
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/status_macros.h"
diff --git a/tensorflow/compiler/tf2xla/lib/scatter.h b/tensorflow/compiler/tf2xla/lib/scatter.h
index 87309e10ed..13a5f1b850 100644
--- a/tensorflow/compiler/tf2xla/lib/scatter.h
+++ b/tensorflow/compiler/tf2xla/lib/scatter.h
@@ -18,8 +18,8 @@ limitations under the License.
#include <functional>
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/statusor.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/lib/triangular_solve.cc b/tensorflow/compiler/tf2xla/lib/triangular_solve.cc
index 75c0ad7f7e..04fa10108c 100644
--- a/tensorflow/compiler/tf2xla/lib/triangular_solve.cc
+++ b/tensorflow/compiler/tf2xla/lib/triangular_solve.cc
@@ -22,7 +22,8 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/lib/util.h"
#include "tensorflow/compiler/xla/client/lib/constants.h"
#include "tensorflow/compiler/xla/client/lib/numeric.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/status_macros.h"
diff --git a/tensorflow/compiler/tf2xla/lib/triangular_solve.h b/tensorflow/compiler/tf2xla/lib/triangular_solve.h
index 2dce620ba8..555760b7ef 100644
--- a/tensorflow/compiler/tf2xla/lib/triangular_solve.h
+++ b/tensorflow/compiler/tf2xla/lib/triangular_solve.h
@@ -16,8 +16,8 @@ limitations under the License.
#ifndef TENSORFLOW_COMPILER_TF2XLA_LIB_TRIANGULAR_SOLVE_H_
#define TENSORFLOW_COMPILER_TF2XLA_LIB_TRIANGULAR_SOLVE_H_
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/lib/triangular_solve_test.cc b/tensorflow/compiler/tf2xla/lib/triangular_solve_test.cc
index a29496dec4..aeebf16028 100644
--- a/tensorflow/compiler/tf2xla/lib/triangular_solve_test.cc
+++ b/tensorflow/compiler/tf2xla/lib/triangular_solve_test.cc
@@ -20,7 +20,7 @@ limitations under the License.
#include <vector>
#include "tensorflow/compiler/xla/array2d.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/compiler/xla/test.h"
diff --git a/tensorflow/compiler/tf2xla/lib/util.cc b/tensorflow/compiler/tf2xla/lib/util.cc
index a6f5d346cb..8b5beba383 100644
--- a/tensorflow/compiler/tf2xla/lib/util.cc
+++ b/tensorflow/compiler/tf2xla/lib/util.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include <memory>
#include <vector>
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/compiler/xla/shape_util.h"
diff --git a/tensorflow/compiler/tf2xla/lib/util.h b/tensorflow/compiler/tf2xla/lib/util.h
index 6cb6c088e9..b4905c9528 100644
--- a/tensorflow/compiler/tf2xla/lib/util.h
+++ b/tensorflow/compiler/tf2xla/lib/util.h
@@ -16,8 +16,8 @@ limitations under the License.
#ifndef TENSORFLOW_COMPILER_TF2XLA_LIB_UTIL_H_
#define TENSORFLOW_COMPILER_TF2XLA_LIB_UTIL_H_
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/core/lib/gtl/array_slice.h"
diff --git a/tensorflow/compiler/tf2xla/lib/while_loop.cc b/tensorflow/compiler/tf2xla/lib/while_loop.cc
index 574e70ddee..d64394f140 100644
--- a/tensorflow/compiler/tf2xla/lib/while_loop.cc
+++ b/tensorflow/compiler/tf2xla/lib/while_loop.cc
@@ -15,7 +15,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/lib/while_loop.h"
#include "tensorflow/compiler/tf2xla/lib/util.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/status_macros.h"
diff --git a/tensorflow/compiler/tf2xla/lib/while_loop.h b/tensorflow/compiler/tf2xla/lib/while_loop.h
index 5b6684c995..9493b1f109 100644
--- a/tensorflow/compiler/tf2xla/lib/while_loop.h
+++ b/tensorflow/compiler/tf2xla/lib/while_loop.h
@@ -19,8 +19,8 @@ limitations under the License.
#include <functional>
#include <vector>
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/core/lib/core/stringpiece.h"
#include "tensorflow/core/lib/gtl/array_slice.h"
diff --git a/tensorflow/compiler/tf2xla/tf2xla.cc b/tensorflow/compiler/tf2xla/tf2xla.cc
index ac768b206e..48568c825b 100644
--- a/tensorflow/compiler/tf2xla/tf2xla.cc
+++ b/tensorflow/compiler/tf2xla/tf2xla.cc
@@ -27,6 +27,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/tf2xla_util.h"
#include "tensorflow/compiler/tf2xla/xla_compiler.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/core/common_runtime/function.h"
#include "tensorflow/core/framework/function.h"
#include "tensorflow/core/framework/graph.pb.h"
diff --git a/tensorflow/compiler/tf2xla/tf2xla.h b/tensorflow/compiler/tf2xla/tf2xla.h
index d02fc56c5b..432a12a516 100644
--- a/tensorflow/compiler/tf2xla/tf2xla.h
+++ b/tensorflow/compiler/tf2xla/tf2xla.h
@@ -18,7 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/tf2xla.pb.h"
#include "tensorflow/compiler/xla/client/client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/core/framework/graph.pb.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/tf2xla_test.cc b/tensorflow/compiler/tf2xla/tf2xla_test.cc
index f0b30dcf4e..56f7045a98 100644
--- a/tensorflow/compiler/tf2xla/tf2xla_test.cc
+++ b/tensorflow/compiler/tf2xla/tf2xla_test.cc
@@ -18,6 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/tf2xla.pb.h"
#include "tensorflow/compiler/xla/client/client_library.h"
#include "tensorflow/compiler/xla/client/local_client.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/compiler/xla/statusor.h"
diff --git a/tensorflow/compiler/tf2xla/xla_compilation_device.cc b/tensorflow/compiler/tf2xla/xla_compilation_device.cc
index fe7ec633ec..e89f473328 100644
--- a/tensorflow/compiler/tf2xla/xla_compilation_device.cc
+++ b/tensorflow/compiler/tf2xla/xla_compilation_device.cc
@@ -22,7 +22,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/sharding_util.h"
#include "tensorflow/compiler/tf2xla/xla_context.h"
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/common_runtime/local_device.h"
#include "tensorflow/core/framework/device_base.h"
#include "tensorflow/core/platform/mem.h"
diff --git a/tensorflow/compiler/tf2xla/xla_compilation_device.h b/tensorflow/compiler/tf2xla/xla_compilation_device.h
index d0b9e34e16..a6e7882533 100644
--- a/tensorflow/compiler/tf2xla/xla_compilation_device.h
+++ b/tensorflow/compiler/tf2xla/xla_compilation_device.h
@@ -19,7 +19,7 @@ limitations under the License.
#include <memory>
#include "tensorflow/compiler/tf2xla/xla_resource.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/xla_data.pb.h"
#include "tensorflow/core/common_runtime/local_device.h"
#include "tensorflow/core/framework/device_base.h"
diff --git a/tensorflow/compiler/tf2xla/xla_compiler.cc b/tensorflow/compiler/tf2xla/xla_compiler.cc
index cb47581e36..226c89bcf1 100644
--- a/tensorflow/compiler/tf2xla/xla_compiler.cc
+++ b/tensorflow/compiler/tf2xla/xla_compiler.cc
@@ -28,12 +28,14 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_compilation_device.h"
#include "tensorflow/compiler/tf2xla/xla_context.h"
#include "tensorflow/compiler/xla/client/client_library.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/core/common_runtime/device.h"
#include "tensorflow/core/common_runtime/executor.h"
#include "tensorflow/core/common_runtime/function.h"
#include "tensorflow/core/common_runtime/graph_optimizer.h"
#include "tensorflow/core/framework/attr_value_util.h"
+#include "tensorflow/core/framework/node_def_util.h"
#include "tensorflow/core/graph/algorithm.h"
#include "tensorflow/core/graph/graph_constructor.h"
#include "tensorflow/core/graph/node_builder.h"
@@ -688,12 +690,12 @@ Status ValidateFunctionDef(const FunctionDef* fdef,
Status ValidateGraph(const Graph* graph,
const FunctionLibraryDefinition& flib_def,
const DeviceType& device_type, const string& name) {
- auto maybe_error = [&](const string& op, const Status& s) -> Status {
+ auto maybe_error = [&](const Node* node, const Status& s) -> Status {
if (!s.ok()) {
return errors::InvalidArgument(strings::StrCat(
"Detected unsupported operations when trying to compile graph ", name,
- " on ", device_type.type_string(), ": ", op, " (", s.error_message(),
- ")"));
+ " on ", device_type.type_string(), ": ", node->def().op(), " (",
+ s.error_message(), ")", FormatNodeForError(*node)));
}
return Status::OK();
};
@@ -706,15 +708,15 @@ Status ValidateGraph(const Graph* graph,
Status s;
if (fdef) {
s = ValidateFunctionDef(fdef, flib_def);
- TF_RETURN_IF_ERROR(maybe_error(node->def().op(), s));
+ TF_RETURN_IF_ERROR(maybe_error(node, s));
continue;
}
const OpDef* op_def;
s = OpRegistry::Global()->LookUpOpDef(node->def().op(), &op_def);
- TF_RETURN_IF_ERROR(maybe_error(node->def().op(), s));
+ TF_RETURN_IF_ERROR(maybe_error(node, s));
TF_RETURN_IF_ERROR(ValidateNodeDef(node->def(), *op_def));
s = FindKernelDef(device_type, node->def(), nullptr, nullptr);
- TF_RETURN_IF_ERROR(maybe_error(node->def().op(), s));
+ TF_RETURN_IF_ERROR(maybe_error(node, s));
}
return Status::OK();
}
diff --git a/tensorflow/compiler/tf2xla/xla_compiler.h b/tensorflow/compiler/tf2xla/xla_compiler.h
index 079c99797e..25332c8d8e 100644
--- a/tensorflow/compiler/tf2xla/xla_compiler.h
+++ b/tensorflow/compiler/tf2xla/xla_compiler.h
@@ -20,6 +20,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_compilation_device.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/compiler/xla/client/local_client.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/status_macros.h"
#include "tensorflow/core/common_runtime/device.h"
#include "tensorflow/core/common_runtime/device_mgr.h"
@@ -251,6 +252,12 @@ class XlaCompiler {
// The default empty value is invalid.
DeviceType device_type = DeviceType("");
+ // The device to use during compilation to execute instructions on, for
+ // example for auto-tuning.
+ // Valid values are defined by `xla::Backend::devices_ordinal_supported()`.
+ // -1 indicates the default device should be used.
+ int device_ordinal = -1;
+
xla::Client* client = nullptr;
// Function library in which to find function definitions. Must be non-null.
diff --git a/tensorflow/compiler/tf2xla/xla_compiler_test.cc b/tensorflow/compiler/tf2xla/xla_compiler_test.cc
index 2fb93be01d..be00ed8813 100644
--- a/tensorflow/compiler/tf2xla/xla_compiler_test.cc
+++ b/tensorflow/compiler/tf2xla/xla_compiler_test.cc
@@ -312,7 +312,7 @@ TEST_F(XlaCompilerTest, HasSaneErrorOnNonCompileTimeConstantInputToReshape) {
str_util::StrContains(status.error_message(), "depends on a parameter"))
<< status.error_message();
EXPECT_TRUE(
- str_util::StrContains(status.error_message(), "[[Node: C = Reshape"))
+ str_util::StrContains(status.error_message(), "[[{{node C}} = Reshape"))
<< status.error_message();
}
@@ -1077,6 +1077,8 @@ TEST_F(XlaCompilerTest, FunctionWithInvalidOp) {
ASSERT_FALSE(status.ok());
EXPECT_TRUE(str_util::StrContains(status.error_message(), "InvalidOp"))
<< status.error_message();
+ EXPECT_TRUE(str_util::StrContains(status.error_message(), "{{node fill_fn}}"))
+ << status.error_message();
}
// Tests a graph which has a node with invalid data type.
@@ -1101,6 +1103,8 @@ TEST_F(XlaCompilerTest, NodeWithInvalidDataType) {
EXPECT_TRUE(str_util::StrContains(status.error_message(),
"is not in the list of allowed values"))
<< status.error_message();
+ EXPECT_TRUE(str_util::StrContains(status.error_message(), "{{node Shape}}"))
+ << status.error_message();
}
TEST_F(XlaCompilerTest, SingleOpWithoutInputs) {
@@ -1122,9 +1126,10 @@ TEST_F(XlaCompilerTest, SingleOpWithoutInputs) {
status = compiler.CompileGraph(XlaCompiler::CompileOptions(), "NoOp",
std::move(graph_copy), args, &result);
ASSERT_FALSE(status.ok());
- EXPECT_TRUE(str_util::StrContains(status.error_message(),
- "The following nodes are unreachable "
- "from the source in the graph: NoOp"))
+ EXPECT_TRUE(
+ str_util::StrContains(status.error_message(),
+ "The following nodes are unreachable "
+ "from the source in the graph: {{node NoOp}}"))
<< status.error_message();
}
diff --git a/tensorflow/compiler/tf2xla/xla_context.cc b/tensorflow/compiler/tf2xla/xla_context.cc
index 0dea366476..b24e3aabbe 100644
--- a/tensorflow/compiler/tf2xla/xla_context.cc
+++ b/tensorflow/compiler/tf2xla/xla_context.cc
@@ -25,7 +25,8 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
#include "tensorflow/compiler/tf2xla/xla_op_kernel.h"
#include "tensorflow/compiler/xla/client/client_library.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/layout_util.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/statusor.h"
diff --git a/tensorflow/compiler/tf2xla/xla_context.h b/tensorflow/compiler/tf2xla/xla_context.h
index 38d8cd653c..3db37afdba 100644
--- a/tensorflow/compiler/tf2xla/xla_context.h
+++ b/tensorflow/compiler/tf2xla/xla_context.h
@@ -22,8 +22,8 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_compilation_device.h"
#include "tensorflow/compiler/tf2xla/xla_compiler.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/status_macros.h"
#include "tensorflow/compiler/xla/xla_data.pb.h"
#include "tensorflow/core/framework/op_kernel.h"
diff --git a/tensorflow/compiler/tf2xla/xla_gpu_backend.cc b/tensorflow/compiler/tf2xla/xla_gpu_backend.cc
index dc98d4fda6..1398e9ee53 100644
--- a/tensorflow/compiler/tf2xla/xla_gpu_backend.cc
+++ b/tensorflow/compiler/tf2xla/xla_gpu_backend.cc
@@ -13,7 +13,6 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
-#include "tensorflow/compiler/tf2xla/legacy_flags/backend_registration_flags.h"
#include "tensorflow/compiler/tf2xla/tf2xla_util.h"
#include "tensorflow/compiler/tf2xla/xla_op_registry.h"
#include "tensorflow/core/framework/kernel_def.pb.h"
@@ -21,20 +20,6 @@ limitations under the License.
namespace tensorflow {
bool GpuOpFilter(KernelDef* kdef) {
- // TODO(b/31361304): The GPU backend does not parallelize PRNG ops, leading to
- // slow code.
- legacy_flags::BackendRegistrationFlags* flags =
- legacy_flags::GetBackendRegistrationFlags();
- VLOG(2) << "flags->tf_enable_prng_ops_gpu: " << flags->tf_enable_prng_ops_gpu;
- if (!flags->tf_enable_prng_ops_gpu &&
- (kdef->op() == "RandomStandardNormal" || kdef->op() == "RandomUniform" ||
- kdef->op() == "RandomUniformInt" || kdef->op() == "TruncatedNormal")) {
- return false;
- }
- // TODO(b/26783907): The GPU backend currently does not implement sort.
- if (kdef->op() == "XlaSort" || kdef->op() == "TopKV2") {
- return false;
- }
if (kdef->op() == "Const") {
AddDtypeToKernalDefConstraint("dtype", DT_STRING, kdef);
}
diff --git a/tensorflow/compiler/tf2xla/xla_helpers.cc b/tensorflow/compiler/tf2xla/xla_helpers.cc
index 4d1b3b1a13..8efb3d55c8 100644
--- a/tensorflow/compiler/tf2xla/xla_helpers.cc
+++ b/tensorflow/compiler/tf2xla/xla_helpers.cc
@@ -26,7 +26,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/lib/constants.h"
#include "tensorflow/compiler/xla/client/lib/numeric.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/types.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/lib/core/status.h"
diff --git a/tensorflow/compiler/tf2xla/xla_helpers.h b/tensorflow/compiler/tf2xla/xla_helpers.h
index d6ca4ab934..e6522157a5 100644
--- a/tensorflow/compiler/tf2xla/xla_helpers.h
+++ b/tensorflow/compiler/tf2xla/xla_helpers.h
@@ -19,7 +19,7 @@ limitations under the License.
#define TENSORFLOW_COMPILER_TF2XLA_XLA_HELPERS_H_
#include "tensorflow/compiler/tf2xla/xla_context.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/lib/gtl/array_slice.h"
diff --git a/tensorflow/compiler/tf2xla/xla_jit_compiled_cpu_function.cc b/tensorflow/compiler/tf2xla/xla_jit_compiled_cpu_function.cc
index 9e17756b27..00ccfb1c78 100644
--- a/tensorflow/compiler/tf2xla/xla_jit_compiled_cpu_function.cc
+++ b/tensorflow/compiler/tf2xla/xla_jit_compiled_cpu_function.cc
@@ -23,6 +23,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/xla_compiled_cpu_function.h"
#include "tensorflow/compiler/xla/client/client_library.h"
#include "tensorflow/compiler/xla/client/local_client.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/service/cpu/cpu_executable.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/xla_data.pb.h"
diff --git a/tensorflow/compiler/tf2xla/xla_op_kernel.cc b/tensorflow/compiler/tf2xla/xla_op_kernel.cc
index e8eafb3819..82028c8b9c 100644
--- a/tensorflow/compiler/tf2xla/xla_op_kernel.cc
+++ b/tensorflow/compiler/tf2xla/xla_op_kernel.cc
@@ -21,7 +21,8 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/shape_util.h"
#include "tensorflow/compiler/tf2xla/type_util.h"
#include "tensorflow/compiler/tf2xla/xla_context.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/status_macros.h"
#include "tensorflow/core/common_runtime/dma_helper.h"
diff --git a/tensorflow/compiler/tf2xla/xla_op_kernel.h b/tensorflow/compiler/tf2xla/xla_op_kernel.h
index 6203cffd80..ac9dfe3369 100644
--- a/tensorflow/compiler/tf2xla/xla_op_kernel.h
+++ b/tensorflow/compiler/tf2xla/xla_op_kernel.h
@@ -17,7 +17,8 @@ limitations under the License.
#define TENSORFLOW_COMPILER_TF2XLA_XLA_OP_KERNEL_H_
#include "tensorflow/compiler/tf2xla/xla_compiler.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/xla_data.pb.h"
#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/platform/macros.h"
diff --git a/tensorflow/compiler/tf2xla/xla_resource.cc b/tensorflow/compiler/tf2xla/xla_resource.cc
index baea814965..7928fa0347 100644
--- a/tensorflow/compiler/tf2xla/xla_resource.cc
+++ b/tensorflow/compiler/tf2xla/xla_resource.cc
@@ -22,7 +22,7 @@ limitations under the License.
#include "tensorflow/compiler/tf2xla/sharding_util.h"
#include "tensorflow/compiler/tf2xla/xla_context.h"
#include "tensorflow/compiler/tf2xla/xla_helpers.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
namespace tensorflow {
diff --git a/tensorflow/compiler/tf2xla/xla_resource.h b/tensorflow/compiler/tf2xla/xla_resource.h
index 4de18a7788..2438490be1 100644
--- a/tensorflow/compiler/tf2xla/xla_resource.h
+++ b/tensorflow/compiler/tf2xla/xla_resource.h
@@ -18,7 +18,7 @@ limitations under the License.
#include <memory>
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/xla_data.pb.h"
#include "tensorflow/core/framework/tensor_shape.h"
#include "tensorflow/core/framework/types.pb.h"
diff --git a/tensorflow/compiler/xla/BUILD b/tensorflow/compiler/xla/BUILD
index f1c383fd9e..fdf13bb18c 100644
--- a/tensorflow/compiler/xla/BUILD
+++ b/tensorflow/compiler/xla/BUILD
@@ -636,7 +636,7 @@ cc_library(
":window_util",
":xla_data_proto",
"//tensorflow/compiler/xla/client:padding",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/service:hlo",
"//tensorflow/compiler/xla/service:hlo_evaluator",
"//tensorflow/compiler/xla/service:shape_inference",
diff --git a/tensorflow/compiler/xla/client/BUILD b/tensorflow/compiler/xla/client/BUILD
index 25666cad40..ad3fcee05b 100644
--- a/tensorflow/compiler/xla/client/BUILD
+++ b/tensorflow/compiler/xla/client/BUILD
@@ -64,6 +64,7 @@ cc_library(
hdrs = ["client.h"],
deps = [
":global_data",
+ ":xla_computation",
"//tensorflow/compiler/xla:execution_options_util",
"//tensorflow/compiler/xla:literal",
"//tensorflow/compiler/xla:service_interface",
@@ -73,7 +74,6 @@ cc_library(
"//tensorflow/compiler/xla:util",
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla:xla_proto",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/compiler/xla/legacy_flags:debug_options_flags",
"//tensorflow/compiler/xla/service:hlo_proto",
"//tensorflow/core:lib",
@@ -100,12 +100,12 @@ cc_library(
deps = [
":client",
":executable_build_options",
+ ":xla_computation",
"//tensorflow/compiler/xla:executable_run_options",
"//tensorflow/compiler/xla:status_macros",
"//tensorflow/compiler/xla:statusor",
"//tensorflow/compiler/xla:util",
"//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/compiler/xla/service:backend",
"//tensorflow/compiler/xla/service:compiler",
"//tensorflow/compiler/xla/service:device_memory_allocator",
@@ -114,6 +114,7 @@ cc_library(
"//tensorflow/compiler/xla/service:local_service",
"//tensorflow/compiler/xla/service:shaped_buffer",
"//tensorflow/compiler/xla/service:source_map_util",
+ "//tensorflow/compiler/xla/service:stream_pool",
"//tensorflow/core:lib",
"//tensorflow/core:stream_executor_no_cuda",
"@llvm//:support",
@@ -126,11 +127,11 @@ cc_library(
hdrs = ["compile_only_client.h"],
deps = [
":client",
+ ":xla_computation",
"//tensorflow/compiler/xla:status_macros",
"//tensorflow/compiler/xla:statusor",
"//tensorflow/compiler/xla:util",
"//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/compiler/xla/service:compile_only_service",
"//tensorflow/compiler/xla/service:compiler",
"//tensorflow/core:stream_executor_no_cuda",
@@ -174,3 +175,60 @@ cc_library(
"//tensorflow/compiler/xla:xla_data_proto",
],
)
+
+cc_library(
+ name = "xla_computation",
+ srcs = ["xla_computation.cc"],
+ hdrs = ["xla_computation.h"],
+ visibility = ["//visibility:public"],
+ deps = [
+ "//tensorflow/compiler/xla:status_macros",
+ "//tensorflow/compiler/xla:util",
+ "//tensorflow/compiler/xla:xla_data_proto",
+ "//tensorflow/compiler/xla/service:hlo_proto",
+ ],
+)
+
+cc_library(
+ name = "xla_builder",
+ srcs = ["xla_builder.cc"],
+ hdrs = ["xla_builder.h"],
+ visibility = ["//visibility:public"],
+ deps = [
+ ":padding",
+ ":sharding_builder",
+ ":xla_computation",
+ "//tensorflow/compiler/xla:execution_options_util",
+ "//tensorflow/compiler/xla:literal",
+ "//tensorflow/compiler/xla:literal_util",
+ "//tensorflow/compiler/xla:shape_util",
+ "//tensorflow/compiler/xla:status_macros",
+ "//tensorflow/compiler/xla:statusor",
+ "//tensorflow/compiler/xla:types",
+ "//tensorflow/compiler/xla:util",
+ "//tensorflow/compiler/xla:xla_data_proto",
+ "//tensorflow/compiler/xla/service:hlo",
+ "//tensorflow/compiler/xla/service:hlo_proto",
+ "//tensorflow/compiler/xla/service:shape_inference",
+ "//tensorflow/core:lib",
+ ],
+)
+
+tf_cc_test(
+ name = "xla_builder_test",
+ srcs = ["xla_builder_test.cc"],
+ deps = [
+ ":xla_builder",
+ ":xla_computation",
+ "//tensorflow/compiler/xla:literal",
+ "//tensorflow/compiler/xla:shape_util",
+ "//tensorflow/compiler/xla:status_macros",
+ "//tensorflow/compiler/xla:test",
+ "//tensorflow/compiler/xla:test_helpers",
+ "//tensorflow/compiler/xla:xla_data_proto",
+ "//tensorflow/compiler/xla/legacy_flags:debug_options_flags",
+ "//tensorflow/compiler/xla/service:hlo",
+ "//tensorflow/compiler/xla/service:hlo_matchers",
+ "//tensorflow/core:test",
+ ],
+)
diff --git a/tensorflow/compiler/xla/client/client.cc b/tensorflow/compiler/xla/client/client.cc
index 8e54311bad..d0ce5e8a6a 100644
--- a/tensorflow/compiler/xla/client/client.cc
+++ b/tensorflow/compiler/xla/client/client.cc
@@ -18,6 +18,7 @@ limitations under the License.
#include <string>
#include <utility>
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/execution_options_util.h"
#include "tensorflow/compiler/xla/legacy_flags/debug_options_flags.h"
#include "tensorflow/compiler/xla/literal.h"
diff --git a/tensorflow/compiler/xla/client/client.h b/tensorflow/compiler/xla/client/client.h
index d751e183dd..be50cebfcc 100644
--- a/tensorflow/compiler/xla/client/client.h
+++ b/tensorflow/compiler/xla/client/client.h
@@ -20,7 +20,7 @@ limitations under the License.
#include <vector>
#include "tensorflow/compiler/xla/client/global_data.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/service/hlo.pb.h"
#include "tensorflow/compiler/xla/service_interface.h"
diff --git a/tensorflow/compiler/xla/client/compile_only_client.h b/tensorflow/compiler/xla/client/compile_only_client.h
index 332c965036..a551edeab0 100644
--- a/tensorflow/compiler/xla/client/compile_only_client.h
+++ b/tensorflow/compiler/xla/client/compile_only_client.h
@@ -17,7 +17,7 @@ limitations under the License.
#define TENSORFLOW_COMPILER_XLA_CLIENT_COMPILE_ONLY_CLIENT_H_
#include "tensorflow/compiler/xla/client/client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/service/compile_only_service.h"
#include "tensorflow/compiler/xla/service/compiler.h"
#include "tensorflow/compiler/xla/statusor.h"
diff --git a/tensorflow/compiler/xla/client/lib/BUILD b/tensorflow/compiler/xla/client/lib/BUILD
index 77ba474cf6..39d5582d19 100644
--- a/tensorflow/compiler/xla/client/lib/BUILD
+++ b/tensorflow/compiler/xla/client/lib/BUILD
@@ -29,8 +29,8 @@ cc_library(
"//tensorflow/compiler/xla:status_macros",
"//tensorflow/compiler/xla:types",
"//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/core:lib",
],
)
@@ -45,7 +45,7 @@ cc_library(
"//tensorflow/compiler/xla:types",
"//tensorflow/compiler/xla:util",
"//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
],
)
@@ -58,7 +58,7 @@ xla_test(
"//tensorflow/compiler/xla:test",
"//tensorflow/compiler/xla:types",
"//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
],
@@ -72,7 +72,7 @@ cc_library(
":constants",
"//tensorflow/compiler/xla:shape_util",
"//tensorflow/compiler/xla:status_macros",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
],
)
@@ -86,7 +86,7 @@ xla_test(
"//tensorflow/compiler/xla:test",
"//tensorflow/compiler/xla:types",
"//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
],
@@ -101,7 +101,7 @@ cc_library(
":constants",
"//tensorflow/compiler/xla:types",
"//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/core:lib",
],
)
@@ -115,7 +115,7 @@ xla_test(
"//tensorflow/compiler/xla:test",
"//tensorflow/compiler/xla:types",
"//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
],
@@ -131,12 +131,43 @@ cc_library(
":numeric",
"//tensorflow/compiler/xla:util",
"//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/core:lib",
],
)
cc_library(
+ name = "sorting",
+ srcs = ["sorting.cc"],
+ hdrs = ["sorting.h"],
+ deps = [
+ ":numeric",
+ "//tensorflow/compiler/xla:types",
+ "//tensorflow/compiler/xla:xla_data_proto",
+ "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ ],
+)
+
+xla_test(
+ name = "sorting_test",
+ srcs = ["sorting_test.cc"],
+ blacklisted_backends = [
+ "cpu",
+ "gpu",
+ ],
+ tags = ["enable_for_xla_interpreter"],
+ deps = [
+ ":sorting",
+ "//tensorflow/compiler/xla:test",
+ "//tensorflow/compiler/xla:types",
+ "//tensorflow/compiler/xla:xla_data_proto",
+ "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/tests:client_library_test_base",
+ "//tensorflow/compiler/xla/tests:xla_internal_test_main",
+ ],
+)
+
+cc_library(
name = "testing",
srcs = ["testing.cc"],
hdrs = ["testing.h"],
@@ -150,8 +181,8 @@ cc_library(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client",
"//tensorflow/compiler/xla/client:global_data",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:test_utils",
"//tensorflow/core:lib",
],
diff --git a/tensorflow/compiler/xla/client/lib/arithmetic.cc b/tensorflow/compiler/xla/client/lib/arithmetic.cc
index de1d785e19..9225b1acd6 100644
--- a/tensorflow/compiler/xla/client/lib/arithmetic.cc
+++ b/tensorflow/compiler/xla/client/lib/arithmetic.cc
@@ -18,8 +18,8 @@ limitations under the License.
#include <string>
#include "tensorflow/compiler/xla/client/lib/constants.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/status_macros.h"
#include "tensorflow/compiler/xla/types.h"
diff --git a/tensorflow/compiler/xla/client/lib/arithmetic.h b/tensorflow/compiler/xla/client/lib/arithmetic.h
index 8367e09450..632e8cc8bc 100644
--- a/tensorflow/compiler/xla/client/lib/arithmetic.h
+++ b/tensorflow/compiler/xla/client/lib/arithmetic.h
@@ -18,8 +18,8 @@ limitations under the License.
#include <memory>
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/xla_data.pb.h"
namespace xla {
diff --git a/tensorflow/compiler/xla/client/lib/constants.h b/tensorflow/compiler/xla/client/lib/constants.h
index b47f5243f0..0c8a9b8cc0 100644
--- a/tensorflow/compiler/xla/client/lib/constants.h
+++ b/tensorflow/compiler/xla/client/lib/constants.h
@@ -18,7 +18,7 @@ limitations under the License.
#include <type_traits>
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/primitive_util.h"
#include "tensorflow/compiler/xla/types.h"
#include "tensorflow/compiler/xla/xla_data.pb.h"
diff --git a/tensorflow/compiler/xla/client/lib/constants_test.cc b/tensorflow/compiler/xla/client/lib/constants_test.cc
index f1e3439862..f4320f65c1 100644
--- a/tensorflow/compiler/xla/client/lib/constants_test.cc
+++ b/tensorflow/compiler/xla/client/lib/constants_test.cc
@@ -14,7 +14,7 @@ limitations under the License.
==============================================================================*/
#include "tensorflow/compiler/xla/client/lib/constants.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/test.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/test_macros.h"
diff --git a/tensorflow/compiler/xla/client/lib/math.h b/tensorflow/compiler/xla/client/lib/math.h
index d003d529cc..13db232556 100644
--- a/tensorflow/compiler/xla/client/lib/math.h
+++ b/tensorflow/compiler/xla/client/lib/math.h
@@ -16,7 +16,7 @@ limitations under the License.
#ifndef TENSORFLOW_COMPILER_XLA_CLIENT_LIB_MATH_H_
#define TENSORFLOW_COMPILER_XLA_CLIENT_LIB_MATH_H_
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
namespace xla {
diff --git a/tensorflow/compiler/xla/client/lib/math_test.cc b/tensorflow/compiler/xla/client/lib/math_test.cc
index 1df287d7db..14c259a7fa 100644
--- a/tensorflow/compiler/xla/client/lib/math_test.cc
+++ b/tensorflow/compiler/xla/client/lib/math_test.cc
@@ -14,7 +14,7 @@ limitations under the License.
==============================================================================*/
#include "tensorflow/compiler/xla/client/lib/math.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/compiler/xla/test.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
diff --git a/tensorflow/compiler/xla/client/lib/numeric.h b/tensorflow/compiler/xla/client/lib/numeric.h
index 212f658313..efd8cdc257 100644
--- a/tensorflow/compiler/xla/client/lib/numeric.h
+++ b/tensorflow/compiler/xla/client/lib/numeric.h
@@ -16,7 +16,7 @@ limitations under the License.
#ifndef TENSORFLOW_COMPILER_XLA_CLIENT_LIB_NUMERIC_H_
#define TENSORFLOW_COMPILER_XLA_CLIENT_LIB_NUMERIC_H_
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/types.h"
#include "tensorflow/compiler/xla/xla_data.pb.h"
diff --git a/tensorflow/compiler/xla/client/lib/numeric_test.cc b/tensorflow/compiler/xla/client/lib/numeric_test.cc
index f56cadc547..8a96ec68d2 100644
--- a/tensorflow/compiler/xla/client/lib/numeric_test.cc
+++ b/tensorflow/compiler/xla/client/lib/numeric_test.cc
@@ -14,7 +14,7 @@ limitations under the License.
==============================================================================*/
#include "tensorflow/compiler/xla/client/lib/numeric.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/test.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/test_macros.h"
diff --git a/tensorflow/compiler/xla/client/lib/prng.cc b/tensorflow/compiler/xla/client/lib/prng.cc
index 299a6ac2b6..3a744148fb 100644
--- a/tensorflow/compiler/xla/client/lib/prng.cc
+++ b/tensorflow/compiler/xla/client/lib/prng.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/lib/constants.h"
#include "tensorflow/compiler/xla/client/lib/math.h"
#include "tensorflow/compiler/xla/client/lib/numeric.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/util.h"
#include "tensorflow/core/lib/core/casts.h"
diff --git a/tensorflow/compiler/xla/client/lib/prng.h b/tensorflow/compiler/xla/client/lib/prng.h
index ac86390239..ad000b1fa1 100644
--- a/tensorflow/compiler/xla/client/lib/prng.h
+++ b/tensorflow/compiler/xla/client/lib/prng.h
@@ -18,7 +18,7 @@ limitations under the License.
#include <array>
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/xla_data.pb.h"
namespace xla {
diff --git a/tensorflow/compiler/xla/client/lib/sorting.cc b/tensorflow/compiler/xla/client/lib/sorting.cc
new file mode 100644
index 0000000000..a904be259a
--- /dev/null
+++ b/tensorflow/compiler/xla/client/lib/sorting.cc
@@ -0,0 +1,46 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/compiler/xla/client/lib/sorting.h"
+#include "tensorflow/compiler/xla/client/lib/numeric.h"
+
+namespace xla {
+
+XlaOp TopK(XlaOp input, int64 k) {
+ XlaBuilder* const builder = input.builder();
+ return builder->ReportErrorOrReturn([&]() -> StatusOr<XlaOp> {
+ TF_ASSIGN_OR_RETURN(Shape input_shape, builder->GetShape(input));
+ int last_dim = input_shape.dimensions_size() - 1;
+ int last_dim_size = input_shape.dimensions(last_dim);
+
+ XlaOp iota_s32 = Iota(builder, S32, last_dim_size);
+ auto input_dims = input_shape.dimensions();
+ std::vector<int64> broadcast_dims(input_dims.begin(), input_dims.end() - 1);
+ XlaOp broadcast_s32 = Broadcast(iota_s32, broadcast_dims);
+ XlaOp sort_result = Sort(Neg(input), broadcast_s32);
+ std::vector<int64> start_indices(input_shape.dimensions_size(), 0);
+ std::vector<int64> limit_indices(input_dims.begin(), input_dims.end());
+ limit_indices[last_dim] = k;
+ std::vector<int64> strides(input_shape.dimensions_size(), 1);
+
+ XlaOp values = Neg(Slice(GetTupleElement(sort_result, 0), start_indices,
+ limit_indices, strides));
+ XlaOp indices = Slice(GetTupleElement(sort_result, 1), start_indices,
+ limit_indices, strides);
+ return Tuple(builder, {values, indices});
+ });
+}
+
+} // namespace xla
diff --git a/tensorflow/compiler/xla/client/lib/sorting.h b/tensorflow/compiler/xla/client/lib/sorting.h
new file mode 100644
index 0000000000..404b4783c3
--- /dev/null
+++ b/tensorflow/compiler/xla/client/lib/sorting.h
@@ -0,0 +1,31 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#ifndef TENSORFLOW_COMPILER_XLA_CLIENT_LIB_SORTING_H_
+#define TENSORFLOW_COMPILER_XLA_CLIENT_LIB_SORTING_H_
+
+#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/types.h"
+#include "tensorflow/compiler/xla/xla_data.pb.h"
+
+namespace xla {
+
+// Returns a tuple composed of the top `k` values and corresponding indices in
+// `input`. Output values are in descending order, from largest to smallest.
+XlaOp TopK(XlaOp input, int64 k);
+
+} // namespace xla
+
+#endif // TENSORFLOW_COMPILER_XLA_CLIENT_LIB_SORTING_H_
diff --git a/tensorflow/compiler/xla/client/lib/sorting_test.cc b/tensorflow/compiler/xla/client/lib/sorting_test.cc
new file mode 100644
index 0000000000..b6eee762a5
--- /dev/null
+++ b/tensorflow/compiler/xla/client/lib/sorting_test.cc
@@ -0,0 +1,60 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/compiler/xla/client/lib/sorting.h"
+#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/test.h"
+#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
+#include "tensorflow/compiler/xla/tests/test_macros.h"
+#include "tensorflow/compiler/xla/types.h"
+
+namespace xla {
+namespace {
+
+using SortingTest = ClientLibraryTestBase;
+
+XLA_TEST_F(SortingTest, TopK3From8Values) {
+ XlaBuilder builder(TestName());
+ auto x =
+ ConstantR1<float>(&builder, {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0});
+ xla::GetTupleElement(xla::TopK(x, 3), 0);
+ ComputeAndCompareR1<float>(&builder, {7.0, 6.0, 5.0}, {});
+}
+
+XLA_TEST_F(SortingTest, TopK3From8Indices) {
+ XlaBuilder builder(TestName());
+ auto x_rev =
+ ConstantR1<float>(&builder, {7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0, 0.0});
+ xla::GetTupleElement(xla::TopK(x_rev, 3), 1);
+ ComputeAndCompareR1<int>(&builder, {0, 1, 2}, {});
+}
+
+XLA_TEST_F(SortingTest, TopKFullSort) {
+ XlaBuilder builder(TestName());
+ const int kSize = 16;
+ std::mt19937 eng;
+ std::uniform_real_distribution<float> u_dist(0.0, 100.0);
+ auto gen = std::bind(u_dist, eng);
+ std::vector<float> inputs(kSize);
+ std::generate(inputs.begin(), inputs.end(), gen);
+ auto x = ConstantR1<float>(&builder, inputs);
+ xla::GetTupleElement(xla::TopK(x, kSize), 0);
+
+ std::sort(inputs.begin(), inputs.end(), std::greater<float>());
+ ComputeAndCompareR1<float>(&builder, inputs, {});
+}
+
+} // namespace
+} // namespace xla
diff --git a/tensorflow/compiler/xla/client/lib/testing.cc b/tensorflow/compiler/xla/client/lib/testing.cc
index 534c509868..b1a776b8b8 100644
--- a/tensorflow/compiler/xla/client/lib/testing.cc
+++ b/tensorflow/compiler/xla/client/lib/testing.cc
@@ -15,7 +15,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/lib/testing.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/execution_options_util.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/shape_util.h"
diff --git a/tensorflow/compiler/xla/client/lib/testing.h b/tensorflow/compiler/xla/client/lib/testing.h
index dc613099e2..03695ce2a3 100644
--- a/tensorflow/compiler/xla/client/lib/testing.h
+++ b/tensorflow/compiler/xla/client/lib/testing.h
@@ -21,7 +21,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/client.h"
#include "tensorflow/compiler/xla/client/global_data.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/xla_data.pb.h"
namespace xla {
diff --git a/tensorflow/compiler/xla/client/local_client.cc b/tensorflow/compiler/xla/client/local_client.cc
index 5f9710914b..e7250e11d5 100644
--- a/tensorflow/compiler/xla/client/local_client.cc
+++ b/tensorflow/compiler/xla/client/local_client.cc
@@ -18,10 +18,12 @@ limitations under the License.
#include <utility>
#include "llvm/ADT/Triple.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/ptr_util.h"
#include "tensorflow/compiler/xla/service/backend.h"
#include "tensorflow/compiler/xla/service/service_executable_run_options.h"
#include "tensorflow/compiler/xla/service/source_map_util.h"
+#include "tensorflow/compiler/xla/service/stream_pool.h"
#include "tensorflow/compiler/xla/status_macros.h"
using xla::source_map_util::InvalidParameterArgument;
@@ -29,8 +31,8 @@ using xla::source_map_util::InvalidParameterArgument;
namespace xla {
namespace {
-StatusOr<Backend::StreamPtr> BorrowStreamForDevice(int device_ordinal,
- Backend* backend) {
+StatusOr<StreamPool::Ptr> BorrowStreamForDevice(int device_ordinal,
+ Backend* backend) {
if (device_ordinal < 0) {
device_ordinal = backend->default_device_ordinal();
}
@@ -141,7 +143,7 @@ StatusOr<ScopedShapedBuffer> LocalExecutable::Run(
TF_RETURN_IF_ERROR(
ValidateExecutionOptions(arguments, run_options, *backend_));
- Backend::StreamPtr stream;
+ StreamPool::Ptr stream;
if (run_options.stream() == nullptr) {
// NB! The lifetime of `stream` needs to match the lifetime of
// `actual_options` (otherwise we will end up using a returned stream in
diff --git a/tensorflow/compiler/xla/client/local_client.h b/tensorflow/compiler/xla/client/local_client.h
index 4d9e0d7cd9..ae23809261 100644
--- a/tensorflow/compiler/xla/client/local_client.h
+++ b/tensorflow/compiler/xla/client/local_client.h
@@ -20,7 +20,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/client.h"
#include "tensorflow/compiler/xla/client/executable_build_options.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/executable_run_options.h"
#include "tensorflow/compiler/xla/service/compiler.h"
#include "tensorflow/compiler/xla/service/device_memory_allocator.h"
diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc b/tensorflow/compiler/xla/client/xla_builder.cc
index a9a4b3bc5d..53be5a79c2 100644
--- a/tensorflow/compiler/xla/client/xla_client/xla_builder.cc
+++ b/tensorflow/compiler/xla/client/xla_builder.cc
@@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include <functional>
#include <numeric>
@@ -22,6 +22,7 @@ limitations under the License.
#include <utility>
#include "tensorflow/compiler/xla/client/sharding_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/execution_options_util.h"
#include "tensorflow/compiler/xla/service/hlo_opcode.h"
#include "tensorflow/compiler/xla/service/shape_inference.h"
diff --git a/tensorflow/compiler/xla/client/xla_builder.h b/tensorflow/compiler/xla/client/xla_builder.h
new file mode 100644
index 0000000000..ae331407d6
--- /dev/null
+++ b/tensorflow/compiler/xla/client/xla_builder.h
@@ -0,0 +1,2241 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#ifndef TENSORFLOW_COMPILER_XLA_CLIENT_XLA_BUILDER_H_
+#define TENSORFLOW_COMPILER_XLA_CLIENT_XLA_BUILDER_H_
+
+#include <map>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+#include "tensorflow/compiler/xla/client/padding.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
+#include "tensorflow/compiler/xla/literal.h"
+#include "tensorflow/compiler/xla/literal_util.h"
+#include "tensorflow/compiler/xla/service/hlo.pb.h"
+#include "tensorflow/compiler/xla/service/hlo_opcode.h"
+#include "tensorflow/compiler/xla/shape_util.h"
+#include "tensorflow/compiler/xla/status_macros.h"
+#include "tensorflow/compiler/xla/statusor.h"
+#include "tensorflow/compiler/xla/types.h"
+#include "tensorflow/compiler/xla/xla_data.pb.h"
+#include "tensorflow/core/lib/core/stringpiece.h"
+#include "tensorflow/core/lib/gtl/array_slice.h"
+#include "tensorflow/core/lib/gtl/flatset.h"
+#include "tensorflow/core/platform/macros.h"
+#include "tensorflow/core/platform/stacktrace.h"
+#include "tensorflow/core/platform/types.h"
+
+namespace xla {
+
+class XlaBuilder;
+
+// This represents an instruction that has been enqueued using the XlaBuilder.
+// This is used to pass to subsequent computations that depends upon the
+// instruction as an operand.
+class XlaOp {
+ public:
+ XlaOp() : handle_(-1), builder_(nullptr) {
+ static_assert(std::is_trivially_destructible<XlaOp>::value,
+ "XlaOp should be trivially destructible");
+ }
+ ~XlaOp() = default;
+
+ // Precondition: !IsUninitialized().
+ //
+ // It's very common to do foo.builder()->bar(). Without this precondition, if
+ // foo.builder() is null, the call to bar will segfault at some point possibly
+ // deep in the callstack when we finally dereference `this`. The precondition
+ // lets us avoid this tricky-to-debug problem.
+ XlaBuilder* builder() const {
+ CHECK(builder_ != nullptr);
+ return builder_;
+ }
+
+ // Returns true if the XlaOp represents valid, non-erroneous value.
+ bool valid() const { return handle_ >= 0; }
+
+ // Returns true if the XlaOp was created by the XlaOp() constructor and
+ // not returned by a builder.
+ bool IsUninitialized() const { return builder_ == nullptr; }
+
+ bool IsIdenticalTo(const XlaOp& rhs) const {
+ return handle_ == rhs.handle_ && builder_ == rhs.builder_;
+ }
+
+ friend std::ostream& operator<<(std::ostream& out, const XlaOp& op) {
+ out << op.handle();
+ return out;
+ }
+
+ private:
+ explicit XlaOp(XlaBuilder* builder) : handle_(-1), builder_(builder) {}
+ XlaOp(int64 handle, XlaBuilder* builder)
+ : handle_(handle), builder_(builder) {}
+
+ int64 handle() const { return handle_; }
+
+ friend class XlaBuilder;
+
+ // < 0 means "invalid handle".
+ int64 handle_;
+
+ // Not owned. Non-null for any handle returned by XlaBuilder, even if the
+ // handle is invalid.
+ XlaBuilder* builder_;
+};
+
+// Arithmetic operator overloads for the XlaOp type.
+XlaOp operator-(const XlaOp& x);
+XlaOp operator+(const XlaOp& x, const XlaOp& y);
+XlaOp operator-(const XlaOp& x, const XlaOp& y);
+XlaOp operator*(const XlaOp& x, const XlaOp& y);
+XlaOp operator/(const XlaOp& x, const XlaOp& y);
+XlaOp operator%(const XlaOp& x, const XlaOp& y);
+
+// Bitwise operator overloads for the XlaOp type.
+XlaOp operator~(const XlaOp& x);
+XlaOp operator&(const XlaOp& x, const XlaOp& y);
+XlaOp operator|(const XlaOp& x, const XlaOp& y);
+XlaOp operator^(const XlaOp& x, const XlaOp& y);
+XlaOp operator<<(const XlaOp& x, const XlaOp& y);
+// Performs a right arithmetic shift if 'x' is a signed type, otherwise performs
+// a right logical shift.
+XlaOp operator>>(const XlaOp& x, const XlaOp& y);
+
+// We don't overload the relational operators (==, !=, <, <=, >, >=) because the
+// semantics might be surprising since their result types are usually 'bool'.
+// Further programmers may expect == to be a structural equality.
+// We also choose not to overload any of the mutating operators (e.g., +=, -=)
+// because the semantics might be misleading — XLA computations are immutable.
+
+// A convenient interface for building up computations.
+//
+// Thread-compatible.
+class XlaBuilder {
+ public:
+ // computation_name: name to use for the built computation.
+ XlaBuilder(const string& computation_name);
+
+ XlaBuilder(const XlaBuilder&) = delete;
+ XlaBuilder& operator=(const XlaBuilder&) = delete;
+
+ ~XlaBuilder();
+
+ // Returns the computation name.
+ const string& name() const { return name_; }
+
+ // Sets OpMetadata that will be added to all instructions until cleared.
+ //
+ // OpMetadata is often applied to a series of XLA HLO instructions. As a
+ // result, OpMetadata is set on the Computation Builder. All subsequent
+ // instructions generated via this Computation Builder will have the same
+ // OpMetadata attached until a call to ClearOpMetadata.
+ void SetOpMetadata(const OpMetadata& metadata) { metadata_ = metadata; }
+
+ // Clears the HloMetadata state.
+ void ClearOpMetadata() { metadata_.Clear(); }
+
+ // Sets an OpSharding that will be attached to all instructions until cleared.
+ void SetSharding(const OpSharding& sharding) { sharding_ = sharding; }
+
+ // Clears the sharding. Ops will be sharded according to the default placement
+ // policy.
+ void ClearSharding() { sharding_ = tensorflow::gtl::nullopt; }
+
+ // Returns the OpSharding that will be attached to all instructions.
+ const tensorflow::gtl::optional<OpSharding>& sharding() const {
+ return sharding_;
+ }
+
+ // Sets the builder to a mode where it will die immediately when an error is
+ // encountered, rather than producing it in a deferred fashion when Build() is
+ // called (which is the default).
+ void set_die_immediately_on_error(bool enabled) {
+ die_immediately_on_error_ = enabled;
+ }
+
+ // Default dimension numbers used for a 2D convolution.
+ static constexpr int64 kConvBatchDimension = 0;
+ static constexpr int64 kConvFeatureDimension = 1;
+ static constexpr int64 kConvFirstSpatialDimension = 2;
+ static constexpr int64 kConvSecondSpatialDimension = 3;
+ static constexpr int64 kConvKernelOutputDimension = 0;
+ static constexpr int64 kConvKernelInputDimension = 1;
+ static constexpr int64 kConvKernelFirstSpatialDimension = 2;
+ static constexpr int64 kConvKernelSecondSpatialDimension = 3;
+
+ // Creates a default ConvolutionDimensionNumbers. For a 2D convolution, for
+ // the input operand {batch, feature, height, width} = {0, 1, 2, 3} and for
+ // the kernel operand
+ // {output_feature, input_feature, height, width} = {0, 1, 2, 3}.
+ static ConvolutionDimensionNumbers CreateDefaultConvDimensionNumbers(
+ int num_spatial_dims = 2);
+
+ // Returns an error if the convolution dimension numbers have conflicts.
+ static Status Validate(const ConvolutionDimensionNumbers& dnum);
+
+ // Returns a new XlaBuilder whose resultant Computation is used only by this
+ // XlaBuilder. The sub-XlaBuilder has the same die_immediately_on_error
+ // behavior as the parent.
+ std::unique_ptr<XlaBuilder> CreateSubBuilder(const string& computation_name);
+
+ // Builds the computation with the requested operations, or returns a non-ok
+ // status. Note that all ops that have been enqueued will be moved to the
+ // computation being returned.
+ StatusOr<XlaComputation> Build();
+
+ // Builds the computation with the requested operations, or notes an error in
+ // the parent XlaBuilder and returns an empty computation if building failed.
+ // This function is intended to be used where the returned XlaComputation is
+ // only used by the parent XlaBuilder and hence further operation on the
+ // returned XlaComputation will simply be error'ed out if an error occurred
+ // while building this computation. If the built computation is to be used by
+ // a XlaBuilder other than the parent XlaBuilder then Build() should be used
+ // instead.
+ XlaComputation BuildAndNoteError();
+
+ // Returns a subgraph that roots on the given root. If the root is not a
+ // compile-time constant (see `IsConstant`), returns an error.
+ //
+ // This will copy the needed ops/computations to the subgraph.
+ StatusOr<XlaComputation> BuildConstantSubGraph(const XlaOp& root_op) const;
+
+ // Returns the first error that was encountered while building the
+ // computation. When an error is encountered, by default we return a vacuous
+ // XlaOp and inform the user of the error that occurred while
+ // building the computation when they make a final call to Build().
+ //
+ // See also set_die_immediately_on_error().
+ Status first_error() const { return first_error_; }
+
+ // Returns the shape of the given op.
+ StatusOr<Shape> GetShape(const XlaOp& op) const;
+
+ // Returns the (inferred) result for the current computation's shape.
+ StatusOr<ProgramShape> GetProgramShape() const;
+
+ // Reports an error to the builder, by
+ // * storing it internally and capturing a backtrace if it's the first error
+ // (this deferred value will be produced on the call to
+ // Build()/GetShape()/...)
+ // * dying if die_immediately_on_error_ is true.
+ // Returns an XlaOp with an invalid handle but a valid builder. This value can
+ // be returned in place of a value in APIs that return an XlaOp.
+ XlaOp ReportError(const Status& error);
+
+ // A helper function that converts a StatusOr<XlaOp> into an XlaOp.
+ // If the Status was an error, reports the error to builder and returns an
+ // invalid XlaOp handle.
+ XlaOp ReportErrorOrReturn(const StatusOr<XlaOp>& op);
+
+ // A helper function that runs a function that returns a StatusOr<XlaOp> and
+ // returns an XlaOp.
+ XlaOp ReportErrorOrReturn(const std::function<StatusOr<XlaOp>()>& op_creator);
+
+ // Returns true if 'operand' is a compile-time constant. A compile-time
+ // constant does not depend on any parameters, or on stateful operators such
+ // as `RngNormal` or `Infeed`.
+ //
+ // This tests whether a computation is a compile-time constant without
+ // evaluating the computation.
+ StatusOr<bool> IsConstant(const XlaOp& operand) const;
+
+ private:
+ // Enqueues a "retrieve parameter value" instruction for a parameter that was
+ // passed to the computation.
+ XlaOp Parameter(int64 parameter_number, const Shape& shape,
+ const string& name);
+
+ // Enqueues a constant with the value of the given literal onto the
+ // computation.
+ XlaOp ConstantLiteral(const LiteralSlice& literal);
+
+ // Enqueues a constant onto the computation. Methods are templated on the
+ // native host type (NativeT) which corresponds to a specific XLA
+ // PrimitiveType as given in the following table:
+ //
+ // Native Type PrimitiveType
+ // -----------------------------
+ // bool PRED
+ // int32 S32
+ // int64 S64
+ // uint32 U32
+ // uint64 U64
+ // float F32
+ // double F64
+ //
+ // Note: not all primitive types defined in xla_data.proto have a
+ // corresponding native type yet.
+ template <typename NativeT>
+ XlaOp ConstantR0(NativeT value);
+ template <typename NativeT>
+ XlaOp ConstantR1(tensorflow::gtl::ArraySlice<NativeT> values);
+ XlaOp ConstantR1(const tensorflow::core::Bitmap& values);
+ template <typename NativeT>
+ XlaOp ConstantR2(
+ std::initializer_list<std::initializer_list<NativeT>> values);
+ template <typename NativeT>
+ XlaOp ConstantFromArrayWithLayout(const Array<NativeT>& values,
+ const Layout& layout);
+ template <typename NativeT>
+ XlaOp ConstantFromArray(const Array<NativeT>& values);
+ template <typename NativeT>
+ XlaOp ConstantR2FromArray2DWithLayout(const Array2D<NativeT>& values,
+ const Layout& layout);
+ template <typename NativeT>
+ XlaOp ConstantR2FromArray2D(const Array2D<NativeT>& values);
+ template <typename NativeT>
+ XlaOp ConstantR3FromArray3DWithLayout(const Array3D<NativeT>& values,
+ const Layout& layout);
+ template <typename NativeT>
+ XlaOp ConstantR3FromArray3D(const Array3D<NativeT>& values);
+ template <typename NativeT>
+ XlaOp ConstantR4FromArray4DWithLayout(const Array4D<NativeT>& values,
+ const Layout& layout);
+ template <typename NativeT>
+ XlaOp ConstantR4FromArray4D(const Array4D<NativeT>& values);
+
+ // Enqueues a rank one constant (vector) onto the computation. The vector has
+ // size 'length' and every element has the value 'value'.
+ template <typename NativeT>
+ XlaOp ConstantR1(int64 length, NativeT value);
+
+ // Adds dimensions to an array by duplicating the data in the array.
+ //
+ // The new dimensions are inserted on the left, i.e. if
+ // broadcast_sizes has values {a0, ..., aN} and the operand shape
+ // has dimensions {b0, ..., bM} then the shape of the output has
+ // dimensions {a0, ..., aN, b0, ..., bM}.
+ //
+ // The new dimensions index into copies of the operand, i.e.
+ //
+ // output[i0, ..., iN, j0, ..., jM] = operand[j0, ..., jM]
+ XlaOp Broadcast(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> broadcast_sizes);
+
+ // Performs in-dimension-style broadcast.
+ //
+ // Operand specifies the input to be broadcast. "shape" is expected output
+ // shape. "broadcast_dimensions" are the dimensions to be broadcasting into.
+ // Dimension numbers in broadcast_dimensions map to individual dimensions
+ // of the operand, and specify what dimension of the output shape they
+ // should be broadcast.
+ // e.g.
+ // Say operand = [1, 2], i.e., a 1D tensor with 2 elements.
+ // and dimension of shape is [2,2].
+ // Specifying {1} as brodcast_dimension will generate output
+ // [1 , 2]
+ // [1 , 2]
+ // On the other hand, specifying {0} as broadcast_dimension
+ // will generate output
+ // [1 , 1]
+ // [2 , 2]
+ XlaOp BroadcastInDim(
+ const XlaOp& operand, const Shape& shape,
+ const tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+
+ // Enqueues a pad operation onto the computation that pads the given value on
+ // the edges as well as between the elements of the input. padding_config
+ // specifies the padding amount for each dimension.
+ XlaOp Pad(const XlaOp& operand, const XlaOp& padding_value,
+ const PaddingConfig& padding_config);
+
+ // Enqueues an operation onto the computation that flattens the operand based
+ // on the dimension order (major/slowest-varying to minor/fastest-varying)
+ // given, followed by reshaping it into the shape with the given dimension
+ // sizes (also major to minor). Conceptually, this is a limited form of
+ // "shape casting".
+ XlaOp Reshape(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> dimensions,
+ tensorflow::gtl::ArraySlice<int64> new_sizes);
+
+ // Enqueues an operation onto the computation that collapses the operand, from
+ // first to last dimension (C order), then reshapes it to the given dimension
+ // sizes. Conceptually, this is a limited form of "shape casting".
+ XlaOp Reshape(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> new_sizes);
+
+ // Wrapper for Reshape.
+ // Enqueues an operation to collapse the provided dimensions; e.g. an
+ // operand with dimensions {x=256, y=2, z=2, p=32} can be collapsed to
+ // {x=1024, y=32} by collapsing dims {0, 1, 2}. Collapsing dimensions must
+ // be a consecutive, in-order subsequence of the operand dimensions.
+ //
+ // Note that collapsing a single dimension does nothing:
+ //
+ // {256} collapsing {0} => {256}
+ // {1} collapsing {0} => {1}
+ //
+ // Collapsing multiple dimensions produces a single result dimension:
+ //
+ // {256, 2} collapsing {0,1} => {512}
+ // {256, 2, 3} collapsing {0,1} => {512, 3}
+ //
+ // This could potentially cause data to be moved -- it provides a more
+ // structured form of reshaping than an arbitrary Reshape operation.
+ XlaOp Collapse(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> dimensions);
+
+ // Enqueues a slice operation onto the computation that slices the operand
+ // from the start indices to the limit indices; e.g.
+ //
+ // x
+ // [ 0 1 2 3 ]
+ // y [ 4 5 6 7 ] => slice(start={1, 1}, limit={2, 3}) => [ 5 6 ]
+ // [ 8 9 a b ]
+ //
+ // Note that "limit" means up-to-but-not-including; i.e. [start, limit) in 1D
+ // range notation.
+ // The strides parameter determines the stride over the slice
+ XlaOp Slice(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> start_indices,
+ tensorflow::gtl::ArraySlice<int64> limit_indices,
+ tensorflow::gtl::ArraySlice<int64> strides);
+
+ // Enqueues a slice operation in a given dimension, taking all other
+ // dimensions as they are; e.g. if dimno is 1 from start_index 2 to
+ // limit_index 4 by 1, and the shape is f32[7,8,9], this call is short-hand
+ // for:
+ //
+ // array[:, 2:4:1, :]
+ XlaOp SliceInDim(const XlaOp& operand, int64 start_index, int64 limit_index,
+ int64 stride, int64 dimno);
+
+ // Enqueues a slice operation onto the computation that slices the 'operand'
+ // from dynamic start indices which are passed in 'start_indices'.
+ // The size of the slice in each dimension is passed in 'slice_sizes',
+ // which specify the end point of exclusive slice intervals in each
+ // dimension [start, start + size).
+ // The shape of 'start_indices' must be rank == 1, with dimension size
+ // equal to the rank of the 'operand'.
+ // Slice index calculations are computed modulo input dimension sizes to
+ // prevent dynamic start indices from generating out-of-bound array accesses.
+ XlaOp DynamicSlice(const XlaOp& operand, const XlaOp& start_indices,
+ tensorflow::gtl::ArraySlice<int64> slice_sizes);
+
+ // Enqueues a dynamic update slice operation onto the computation, which
+ // updates a slice of 'operand' with 'update' at dynamic 'start_indices'.
+ // The shape of 'update' determines the shape of the slice of 'operand'
+ // which is updated.
+ // The indices specified in 'start_indices' specify the offset of the slice
+ // of 'operand' which is updated.
+ //
+ // update = {10, 11} // calculated at runtime.
+ // [1 2 3] start = {1, 1} // calculated at runtime. [1 2 3 ]
+ // [4 5 6] => DynamicUpdateslice(data, update, start) => [4 10 11]
+ // [7 8 9] [7 8 9 ]
+ //
+ // The shape of 'start_indices' must be rank == 1, with dimension size
+ // equal to the rank of the 'operand'.
+ // Slice index calculations are computed modulo update dimension sizes to
+ // prevent dynamic start indices from generating out-of-bound array accesses.
+ XlaOp DynamicUpdateSlice(const XlaOp& operand, const XlaOp& update,
+ const XlaOp& start_indices);
+
+ // Enqueues a concatenate instruction onto the computation. 'operands' must
+ // have >= 1 entry.
+ XlaOp ConcatInDim(tensorflow::gtl::ArraySlice<XlaOp> operands,
+ int64 dimension);
+
+ // Enqueue a tracing operation onto the computation; the computation will emit
+ // a logging message with the operand.
+ void Trace(const string& tag, const XlaOp& operand);
+
+ // Enqueues a conditional-move-like select operation onto the computation;
+ // predicated on pred, selects between on_true and on_false.
+ XlaOp Select(const XlaOp& pred, const XlaOp& on_true, const XlaOp& on_false);
+
+ // Enqueues a tuple-creation instruction onto the computation.
+ XlaOp Tuple(tensorflow::gtl::ArraySlice<XlaOp> elements);
+
+ // Enqueues a tuple-element-get instruction onto the computation.
+ XlaOp GetTupleElement(const XlaOp& tuple_data, int64 index);
+
+ // Enqueues an equal-to comparison instruction onto the computation.
+ XlaOp Eq(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ // Enqueues a not-equal comparison instruction onto the computation.
+ XlaOp Ne(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ // Enqueues a greater-or-equal comparison instruction onto the computation.
+ XlaOp Ge(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ // Enqueues a greater-than comparison instruction onto the computation.
+ XlaOp Gt(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ // Enqueues a less-than comparison instruction onto the computation.
+ XlaOp Lt(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ // Enqueues a less-or-equal comparison instruction onto the computation.
+ XlaOp Le(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ // Enqueues a dot instruction onto the computation.
+ XlaOp Dot(const XlaOp& lhs, const XlaOp& rhs);
+
+ // Enqueues a general dot instruction onto the computation.
+ XlaOp DotGeneral(const XlaOp& lhs, const XlaOp& rhs,
+ const DotDimensionNumbers& dimension_numbers);
+
+ // Enqueues a convolution instruction onto the computation, which uses the
+ // default convolution dimension numbers.
+ XlaOp Conv(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ Padding padding);
+
+ // Enqueues a convolution instruction onto the computation, with the caller
+ // provided padding configuration in the format returned by MakePadding().
+ XlaOp ConvWithGeneralPadding(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding);
+
+ // Enqueues a convolution instruction onto the computation, with the caller
+ // provided dimension numbers configuration.
+ XlaOp ConvWithGeneralDimensions(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> window_strides, Padding padding,
+ const ConvolutionDimensionNumbers& dimension_numbers);
+
+ // Enqueues a convolution instruction onto the computation, with the caller
+ // provided padding configuration as well as the dimension numbers.
+ XlaOp ConvGeneral(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
+ const ConvolutionDimensionNumbers& dimension_numbers);
+
+ // Enqueues a convolution instruction onto the computation, with the caller
+ // provided padding configuration, dilation factors and dimension numbers.
+ XlaOp ConvGeneralDilated(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
+ tensorflow::gtl::ArraySlice<int64> lhs_dilation,
+ tensorflow::gtl::ArraySlice<int64> rhs_dilation,
+ const ConvolutionDimensionNumbers& dimension_numbers);
+
+ // Enqueues an FFT instruction onto the computation, of the given type and
+ // with the given FFT length.
+ XlaOp Fft(const XlaOp& operand, FftType fft_type,
+ tensorflow::gtl::ArraySlice<int64> fft_length);
+
+ // Enqueues an infeed instruction onto the computation, which writes data of
+ // the given shape to the infeed buffer of the device.
+ XlaOp Infeed(const Shape& shape, const string& config = "");
+ XlaOp InfeedWithToken(const XlaOp& token, const Shape& shape,
+ const string& config = "");
+
+ // Enqueues an outfeed instruction onto the computation. This instruction
+ // generates outgoing data transfers for the given data.
+ //
+ // shape_with_layout communicates the laid out shape that we want to outfeed
+ // -- if !ShapeUtil::Compatible(GetShape(operand), shape_with_layout) an error
+ // will occur.
+ void Outfeed(const XlaOp& operand, const Shape& shape_with_layout,
+ const string& outfeed_config);
+ XlaOp OutfeedWithToken(const XlaOp& operand, const XlaOp& token,
+ const Shape& shape_with_layout,
+ const string& outfeed_config);
+
+ // Enqueues a call instruction onto the computation.
+ XlaOp Call(const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<XlaOp> operands);
+
+ // Enqueues a custom call instruction onto the computation.
+ // During code generation, a call instruction is emitted which targets a
+ // symbol with the name |call_target_name|. The |operands| are passed to the
+ // call instruction. |shape| is the resultant shape.
+ XlaOp CustomCall(const string& call_target_name,
+ tensorflow::gtl::ArraySlice<XlaOp> operands,
+ const Shape& shape);
+
+ // Enqueues a pseudo-op to represent host-side computation data-dependencies.
+ // During code generation, host send and receive operations will be generated
+ // to transfer |operands| to the host and a single result of |shape| back to
+ // the device. Host send/recv operations are emitted using |channel_name|.
+ // Dataflow dependencies and the |cost_estimate_ns| field may be used in HLO
+ // instruction scheduling.
+ XlaOp HostCompute(tensorflow::gtl::ArraySlice<XlaOp> operands,
+ const string& channel_name, int64 cost_estimate_ns,
+ const Shape& shape);
+
+ // The following methods enqueue element-wise binary arithmetic operations
+ // onto the computation. The shapes of the operands have to match unless one
+ // of the operands is a scalar, or an explicit broadcast dimension is given
+ // (see g3doc for more details).
+
+ // Enqueues a complex compose instruction onto the computation.
+ XlaOp Complex(const XlaOp& real, const XlaOp& imag,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ // Enqueues a complex conjugate instruction onto the computation.
+ XlaOp Conj(const XlaOp& operand);
+
+ // Enqueues an add instruction onto the computation.
+ XlaOp Add(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ // Enqueues a subtract instruction onto the computation.
+ XlaOp Sub(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ // Enqueues a multiply instruction onto the computation.
+ XlaOp Mul(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ // Enqueues a divide instruction onto the computation.
+ XlaOp Div(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ // Enqueues a remainder instruction onto the computation.
+ XlaOp Rem(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ // Enqueues a max instruction onto the computation.
+ XlaOp Max(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ // Enqueues a min instruction onto the computation.
+ XlaOp Min(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ // Element-wise logical operators
+ XlaOp And(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ XlaOp Or(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ XlaOp Xor(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ XlaOp Not(const XlaOp& operand);
+
+ XlaOp ShiftLeft(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+ XlaOp ShiftRightArithmetic(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+ XlaOp ShiftRightLogical(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ // Reduces an array among the provided dimensions, given "computation" as a
+ // reduction operator.
+ XlaOp Reduce(const XlaOp& operand, const XlaOp& init_value,
+ const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<int64> dimensions_to_reduce);
+
+ // Convenience wrapper around the above that reduces all the dimensions in the
+ // operand shape.
+ XlaOp ReduceAll(const XlaOp& operand, const XlaOp& init_value,
+ const XlaComputation& computation);
+
+ // Enqueues a windowed reduce instruction onto the computation.
+ XlaOp ReduceWindow(const XlaOp& operand, const XlaOp& init_value,
+ const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<int64> window_dimensions,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ Padding padding);
+
+ // As ReduceWindow(), but the padding is given in the format
+ // returned by MakePadding().
+ XlaOp ReduceWindowWithGeneralPadding(
+ const XlaOp& operand, const XlaOp& init_value,
+ const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<int64> window_dimensions,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding);
+
+ // Returns the sum of the operand value within each subgroup of replicas. All
+ // replicas supply one input to the sum and all replicas receive the resulting
+ // sum for each subgroup.
+ XlaOp CrossReplicaSum(
+ const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> replica_group_ids = {});
+
+ // Enqueues an operation that do an AllReduce of the operand cross cores. Here
+ // AllReduce means doing a reduction on the input operand cross cores and then
+ // broadcasting the reduction result to those cores. The reduction function is
+ // defined by `computation`, which should be a commutative computation on
+ // scalars, e.g., add, min, or max. The way that AllReduce is applied is
+ // configured by:
+ //
+ // - `replica_group_ids`: maps replica ids to subgroup ids. If empty, all
+ // replicas belong to one group. Allreduce will be applied within subgroups.
+ // For example, we have 4 replicas, then replica_group_ids={0,1,0,1} means,
+ // replica 0 and 2 are in subgroup 0, replica 1 and 3 are in subgroup 1.
+ //
+ // - `channel_id`: for Allreduce nodes from different models, if they have the
+ // same channel_id, they will be 'Allreduce'd. If empty, Allreduce will not be
+ // applied cross models.
+ //
+ // TODO(b/79737069): Rename this to AllReduce when it's ready to use.
+ XlaOp CrossReplicaSum(
+ const XlaOp& operand, const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<int64> replica_group_ids = {},
+ const tensorflow::gtl::optional<ChannelHandle>& channel_id =
+ tensorflow::gtl::nullopt);
+
+ // Enqueues an operation that scatters the `source` array to the selected
+ // indices of each window.
+ XlaOp SelectAndScatter(const XlaOp& operand, const XlaComputation& select,
+ tensorflow::gtl::ArraySlice<int64> window_dimensions,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ Padding padding, const XlaOp& source,
+ const XlaOp& init_value,
+ const XlaComputation& scatter);
+
+ // As SelectAndScatter(), but the padding is given in the format
+ // returned by MakePadding().
+ XlaOp SelectAndScatterWithGeneralPadding(
+ const XlaOp& operand, const XlaComputation& select,
+ tensorflow::gtl::ArraySlice<int64> window_dimensions,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
+ const XlaOp& source, const XlaOp& init_value,
+ const XlaComputation& scatter);
+
+ // Enqueues an abs instruction onto the computation.
+ XlaOp Abs(const XlaOp& operand);
+
+ // Enqueues a atan2 instruction onto the computation.
+ XlaOp Atan2(const XlaOp& y, const XlaOp& x,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ // Enqueues an exp instruction onto the computation.
+ XlaOp Exp(const XlaOp& operand);
+
+ // Enqueues an expm1 instruction onto the computation.
+ XlaOp Expm1(const XlaOp& operand);
+
+ // Enqueues a floor instruction onto the computation.
+ XlaOp Floor(const XlaOp& operand);
+
+ // Enqueues a ceil instruction onto the computation.
+ XlaOp Ceil(const XlaOp& operand);
+
+ // Enqueues a round instruction onto the computation, rounding to nearest even
+ // with half-way cases rounding away from zero.
+ XlaOp Round(const XlaOp& operand);
+
+ // Enqueues an log instruction (natural logarithm) onto the computation.
+ XlaOp Log(const XlaOp& operand);
+
+ // Enqueues an log1p instruction (log(x+1)) onto the computation.
+ XlaOp Log1p(const XlaOp& operand);
+
+ // Enqueues a sign instruction onto the computation.
+ XlaOp Sign(const XlaOp& operand);
+
+ // Enqueues a count leading zeros instruction onto the computation.
+ XlaOp Clz(const XlaOp& operand);
+
+ // Enqueues a cosine instruction onto the computation.
+ XlaOp Cos(const XlaOp& operand);
+
+ // Enqueues a sine instruction onto the computation.
+ XlaOp Sin(const XlaOp& operand);
+
+ // Enqueues a tanh instruction onto the computation.
+ XlaOp Tanh(const XlaOp& operand);
+
+ // Enqueues a real-part instruction onto the computation.
+ XlaOp Real(const XlaOp& operand);
+
+ // Enqueues an imaginary-part instruction onto the computation.
+ XlaOp Imag(const XlaOp& operand);
+
+ // Enqueues a lhs^rhs computation onto the computation.
+ XlaOp Pow(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+ // Enqueues an operator that tests if the operand's values are finite, i.e.,
+ // not Inf or NaN. Defined only for floating-point types. Returns an array of
+ // booleans with the same shape where entries are true iff the corresponding
+ // entry was NaN.
+ XlaOp IsFinite(const XlaOp& operand);
+
+ // Enqueues a convert instruction onto the computation that changes the
+ // element type of the operand array to primitive_type.
+ XlaOp ConvertElementType(const XlaOp& operand,
+ PrimitiveType new_element_type);
+
+ // Enqueues a no-op instruction onto the computation that changes
+ // the element type of the operand array to primitive_type. The
+ // bit-widths of the source and destination element types must be
+ // identical.
+ XlaOp BitcastConvertType(const XlaOp& operand,
+ PrimitiveType new_element_type);
+
+ // Enqueues a negate instruction onto the computation.
+ XlaOp Neg(const XlaOp& operand);
+
+ // Enqueues a transpose instruction onto the computation.
+ XlaOp Transpose(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> permutation);
+
+ // Enqueues a reverse instruction onto the computation. The order of the
+ // elements in the given dimensions is reversed (i.e., the element at index i
+ // is moved to index dimension_size - 1 - i).
+ XlaOp Rev(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> dimensions);
+
+ // Enqueues a sort (as increasing order) instruction onto the computation.
+ // If only keys are provided:
+ // * If the keys are an rank-1 tensor (an array), the result is a sorted array
+ // of keys, in ascending order.
+ // * If the keys have higher rank, the keys are sorted along the provided
+ // dimension. For example, for a rank-2 tensor (a matrix) of keys, a dimension
+ // value of 0 will indepenently sort every column, and a dimension value of 1
+ // will independently sort each row. If no dimension number is provided, then
+ // the last dimension is chosen by default.
+ //
+ // If both keys and values are provided:
+ // * The keys and the values must tensors with the same dimensions. The
+ // element types of the tensors may be different.
+ // * The result is a tuple that consists of a sorted tensor of keys (along the
+ // provided dimension, as above) as the first element, and a tensor with their
+ // corresponding values as the second element.
+ XlaOp Sort(XlaOp keys,
+ tensorflow::gtl::optional<XlaOp> values = tensorflow::gtl::nullopt,
+ int64 dimension = -1);
+
+ // Enqueues a clamp instruction onto the computation.
+ XlaOp Clamp(const XlaOp& min, const XlaOp& operand, const XlaOp& max);
+
+ // Enqueues a map instruction onto the computation.
+ XlaOp Map(tensorflow::gtl::ArraySlice<XlaOp> operands,
+ const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<int64> dimensions,
+ tensorflow::gtl::ArraySlice<XlaOp> static_operands = {});
+
+ // Enqueues a N(mu, sigma) random number generation instruction onto the
+ // computation.
+ XlaOp RngNormal(const XlaOp& mu, const XlaOp& sigma, const Shape& shape);
+
+ // Enqueues a U(a, b) random number generation instruction onto the
+ // computation. Returns values in the semi-open interval [a, b).
+ XlaOp RngUniform(const XlaOp& a, const XlaOp& b, const Shape& shape);
+
+ // Enqueues a while node onto the computation.
+ XlaOp While(const XlaComputation& condition, const XlaComputation& body,
+ const XlaOp& init);
+
+ // Enqueues a conditional node onto the computation.
+ XlaOp Conditional(const XlaOp& predicate, const XlaOp& true_operand,
+ const XlaComputation& true_computation,
+ const XlaOp& false_operand,
+ const XlaComputation& false_computation);
+
+ // Enqueues a ReducePrecision node onto the computation.
+ XlaOp ReducePrecision(const XlaOp& operand, const int exponent_bits,
+ const int mantissa_bits);
+
+ // Enqueues a Gather node onto the computation.
+ XlaOp Gather(const XlaOp& input, const XlaOp& gather_indices,
+ const GatherDimensionNumbers& dimension_numbers,
+ tensorflow::gtl::ArraySlice<int64> window_bounds);
+
+ // Enqueues a Send node onto the computation for device-to-device
+ // communication, to send the given operand to a Recv instruction that shares
+ // the same channel handle.
+ void Send(const XlaOp& operand, const ChannelHandle& handle);
+ XlaOp SendWithToken(const XlaOp& operand, const XlaOp& token,
+ const ChannelHandle& handle);
+
+ // Enqueues a Send node which sends data to the host.
+ XlaOp SendToHost(const XlaOp& operand, const XlaOp& token,
+ const Shape& shape_with_layout, const ChannelHandle& handle);
+
+ // Enqueues a Recv node which receives data from the host.
+ XlaOp RecvFromHost(const XlaOp& token, const Shape& shape,
+ const ChannelHandle& handle);
+
+ // Enqueues an AfterAll operation with no operands producing a token-shaped
+ // value.
+ XlaOp CreateToken();
+
+ // Enqueues an AfterAll operation with no operands producing a token-shaped
+ // value.
+ XlaOp AfterAll(tensorflow::gtl::ArraySlice<XlaOp> tokens);
+
+ // Enqueues a Recv node onto the computation. The data comes from a Send
+ // instruction that shares the same channel handle and its shape must
+ // be the same as the given shape.
+ XlaOp Recv(const Shape& shape, const ChannelHandle& handle);
+ XlaOp RecvWithToken(const XlaOp& token, const Shape& shape,
+ const ChannelHandle& handle);
+
+ // Normalizes operand across spatial and batch dimensions for each feature.
+ //
+ // Returns a tuple (normalized, batch_mean, batch_var) where `normalized`
+ // is the normalized result and batch_mean and batch_var are the mean and
+ // variance, respectively, across batch for the operand.
+ XlaOp BatchNormTraining(const XlaOp& operand, const XlaOp& scale,
+ const XlaOp& offset, float epsilon,
+ int64 feature_index);
+
+ // Normalizes operand across spatial and batch dimensions for each feature.
+ //
+ // `BatchNormInference` is equivalent to calling `BatchNormTraining` without
+ // computing `mean` and `variance` for each batch inside the operation. It
+ // uses the input `mean` and `variance` instead as estimated values. The
+ // purpose of this op is to reduce latency in inference, hence the name
+ // `BatchNormInference`.
+ //
+ // The output has the same shape as `operand`, and contains the normalized
+ // values for each batch.
+ XlaOp BatchNormInference(const XlaOp& operand, const XlaOp& scale,
+ const XlaOp& offset, const XlaOp& mean,
+ const XlaOp& variance, float epsilon,
+ int64 feature_index);
+
+ // Calculates the gradients of a batch norm op.
+ //
+ // The inputs `batch_mean` and `batch_var` represent the mean and variance
+ // across the batch.
+ //
+ // Returns a tuple of three elements:
+ // - grad_operand: Gradient with respect to input `operand`
+ // - grad_offset: Gradient with respect to input `offset`
+ // - grad_scale: Gradient with respect to input `scale`
+ XlaOp BatchNormGrad(const XlaOp& operand, const XlaOp& scale,
+ const XlaOp& batch_mean, const XlaOp& batch_var,
+ const XlaOp& grad_output, float epsilon,
+ int64 feature_index);
+
+ StatusOr<XlaOp> AddInstruction(
+ HloInstructionProto&& instr, HloOpcode opcode,
+ tensorflow::gtl::ArraySlice<XlaOp> operands = {});
+
+ void AddCalledComputation(const XlaComputation& computation,
+ HloInstructionProto* instr);
+
+ StatusOr<const HloInstructionProto*> LookUpInstruction(const XlaOp& op) const;
+
+ // Internal helper method that does the building for an arbitrary unary op.
+ XlaOp UnaryOp(HloOpcode unop, const XlaOp& operand);
+
+ // Internal helper method that does the building for an arbitrary binary op.
+ // broadcast_dimensions specifies which dimensions to use for broadcasting
+ // when the operation is between tensors of different ranks.
+ XlaOp BinaryOp(HloOpcode binop, const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+
+ // Internal helper method that does the building for an arbitrary ternary op.
+ XlaOp TernaryOp(HloOpcode triop, const XlaOp& lhs, const XlaOp& rhs,
+ const XlaOp& ehs);
+
+ XlaOp RngOp(RandomDistribution distribution,
+ tensorflow::gtl::ArraySlice<XlaOp> parameters,
+ const Shape& shape);
+
+ StatusOr<XlaOp> InDimBroadcast(
+ const Shape& shape, const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+
+ // Internal helper method that creates a sequence of instructions that
+ // performs an explicit broadcast of the operand to the target shape.
+ StatusOr<XlaOp> AddBroadcastSequence(const Shape& output_shape,
+ const XlaOp& operand);
+
+ // Internal helper method for creating a Reshape op with the already inferred
+ // shape.
+ StatusOr<XlaOp> Reshape(const Shape& shape, const XlaOp& operand);
+
+ // Returns the (inferred) result for the program shape for the current
+ // computation and fills the root_id in the pointer.
+ StatusOr<ProgramShape> GetProgramShape(int64* root_id) const;
+
+ // Returns shapes for the operands.
+ StatusOr<std::vector<Shape>> GetOperandShapes(
+ tensorflow::gtl::ArraySlice<XlaOp> operands) const;
+
+ // A visitor which checks whether an operation is a compile-time constant,
+ // meaning that it doesn't depend on any parameters, or on any stateful
+ // operation such as `RngNormal` or `Infeed`. The visitor walks the
+ // computation starting at a given operation and sets is_constant to false iff
+ // a parameter or stateful operation is encountered.
+ void IsConstantVisitor(const int64 op_handle, std::set<int64>* visited,
+ bool* is_constant) const;
+
+ // Checks bounds for convolution parameters.
+ Status VerifyConvolution(
+ const Shape& lhs_shape, const Shape& rhs_shape,
+ const ConvolutionDimensionNumbers& dimension_numbers) const;
+
+ // Helper function for creating a Window proto from user-supplied data.
+ // Returns error if the user-supplied data was invalid.
+ StatusOr<Window> MakeWindow(
+ tensorflow::gtl::ArraySlice<int64> window_dimensions,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
+ tensorflow::gtl::ArraySlice<int64> lhs_dilation,
+ tensorflow::gtl::ArraySlice<int64> rhs_dilation) const;
+
+ string name_; // Name to use for the built computation.
+
+ // The first error encountered while building the computation.
+ // This is OK until the first error is encountered.
+ Status first_error_;
+
+ // The saved stack trace from the point at which the first error occurred.
+ tensorflow::SavedStackTrace first_error_backtrace_;
+
+ // The instructions of this computation.
+ std::vector<HloInstructionProto> instructions_;
+
+ // The embedded computations used by this computation. Each computation was
+ // the entry computation of some XlaComputation, the key is the unique id of
+ // that XlaComputation.
+ std::map<int64, HloComputationProto> embedded_;
+
+ // The unique parameter numbers.
+ tensorflow::gtl::FlatSet<int64> parameter_numbers_;
+
+ // The metadata to attach to each op. This is structured as a "modal"-like
+ // operation, in order to simplify client code (and not sprinkle this metadata
+ // throughout the TensorFlow op kernel implementations).
+ OpMetadata metadata_;
+
+ // Sharding for this operator. This is structured as a "model"-like operation,
+ // in order to simplify client code, similar to metadata_.
+ tensorflow::gtl::optional<OpSharding> sharding_;
+
+ // Mode bit that indicates whether to die when a first error is encountered.
+ bool die_immediately_on_error_ = false;
+
+ XlaBuilder* parent_builder_{nullptr};
+
+ friend XlaOp Parameter(XlaBuilder* builder, int64 parameter_number,
+ const Shape& shape, const string& name);
+ friend XlaOp ConstantLiteral(XlaBuilder* builder,
+ const LiteralSlice& literal);
+ template <typename NativeT>
+ friend XlaOp ConstantR0(XlaBuilder* builder, NativeT value);
+ template <typename NativeT>
+ friend XlaOp ConstantR1(XlaBuilder* builder,
+ tensorflow::gtl::ArraySlice<NativeT> values);
+ friend XlaOp ConstantR1(XlaBuilder* builder,
+ const tensorflow::core::Bitmap& values);
+ template <typename NativeT>
+ friend XlaOp ConstantR2(
+ XlaBuilder* builder,
+ std::initializer_list<std::initializer_list<NativeT>> values);
+ template <typename NativeT>
+ friend XlaOp ConstantFromArrayWithLayout(XlaBuilder* builder,
+ const Array<NativeT>& values,
+ const Layout& layout);
+ template <typename NativeT>
+ friend XlaOp ConstantFromArray(XlaBuilder* builder,
+ const Array<NativeT>& values);
+ template <typename NativeT>
+ friend XlaOp ConstantR2FromArray2DWithLayout(XlaBuilder* builder,
+ const Array2D<NativeT>& values,
+ const Layout& layout);
+ template <typename NativeT>
+ friend XlaOp ConstantR2FromArray2D(XlaBuilder* builder,
+ const Array2D<NativeT>& values);
+ template <typename NativeT>
+ friend XlaOp ConstantR3FromArray3DWithLayout(XlaBuilder* builder,
+ const Array3D<NativeT>& values,
+ const Layout& layout);
+ template <typename NativeT>
+ friend XlaOp ConstantR3FromArray3D(XlaBuilder* builder,
+ const Array3D<NativeT>& values);
+ template <typename NativeT>
+ friend XlaOp ConstantR4FromArray4DWithLayout(XlaBuilder* builder,
+ const Array4D<NativeT>& values,
+ const Layout& layout);
+ template <typename NativeT>
+ friend XlaOp ConstantR4FromArray4D(XlaBuilder* builder,
+ const Array4D<NativeT>& values);
+
+ template <typename NativeT>
+ friend XlaOp ConstantR1(XlaBuilder* builder, int64 length, NativeT value);
+
+ friend XlaOp Broadcast(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> broadcast_sizes);
+
+ friend XlaOp BroadcastInDim(
+ const XlaOp& operand, const Shape& shape,
+ const tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+
+ friend XlaOp Pad(const XlaOp& operand, const XlaOp& padding_value,
+ const PaddingConfig& padding_config);
+
+ friend XlaOp Reshape(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> dimensions,
+ tensorflow::gtl::ArraySlice<int64> new_sizes);
+
+ friend XlaOp Reshape(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> new_sizes);
+
+ friend XlaOp Collapse(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> dimensions);
+
+ friend XlaOp Slice(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> start_indices,
+ tensorflow::gtl::ArraySlice<int64> limit_indices,
+ tensorflow::gtl::ArraySlice<int64> strides);
+
+ friend XlaOp SliceInDim(const XlaOp& operand, int64 start_index,
+ int64 limit_index, int64 stride, int64 dimno);
+
+ friend XlaOp DynamicSlice(const XlaOp& operand, const XlaOp& start_indices,
+ tensorflow::gtl::ArraySlice<int64> slice_sizes);
+
+ friend XlaOp DynamicUpdateSlice(const XlaOp& operand, const XlaOp& update,
+ const XlaOp& start_indices);
+
+ friend XlaOp ConcatInDim(XlaBuilder* builder,
+ tensorflow::gtl::ArraySlice<XlaOp> operands,
+ int64 dimension);
+
+ friend void Trace(const string& tag, const XlaOp& operand);
+
+ friend XlaOp Select(const XlaOp& pred, const XlaOp& on_true,
+ const XlaOp& on_false);
+ friend XlaOp Tuple(XlaBuilder* builder,
+ tensorflow::gtl::ArraySlice<XlaOp> elements);
+ friend XlaOp GetTupleElement(const XlaOp& tuple_data, int64 index);
+ friend XlaOp Eq(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Ne(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Ge(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Gt(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Lt(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Le(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Dot(const XlaOp& lhs, const XlaOp& rhs);
+ friend XlaOp DotGeneral(const XlaOp& lhs, const XlaOp& rhs,
+ const DotDimensionNumbers& dimension_numbers);
+ friend XlaOp Conv(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ Padding padding);
+ friend XlaOp ConvWithGeneralPadding(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding);
+ friend XlaOp ConvWithGeneralDimensions(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> window_strides, Padding padding,
+ const ConvolutionDimensionNumbers& dimension_numbers);
+ friend XlaOp ConvGeneral(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
+ const ConvolutionDimensionNumbers& dimension_numbers);
+ friend XlaOp ConvGeneralDilated(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
+ tensorflow::gtl::ArraySlice<int64> lhs_dilation,
+ tensorflow::gtl::ArraySlice<int64> rhs_dilation,
+ const ConvolutionDimensionNumbers& dimension_numbers);
+ friend XlaOp Fft(const XlaOp& operand, FftType fft_type,
+ tensorflow::gtl::ArraySlice<int64> fft_length);
+ friend XlaOp Infeed(XlaBuilder* builder, const Shape& shape,
+ const string& config);
+ friend void Outfeed(const XlaOp& operand, const Shape& shape_with_layout,
+ const string& outfeed_config);
+ friend XlaOp Call(XlaBuilder* builder, const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<XlaOp> operands);
+ friend XlaOp CustomCall(XlaBuilder* builder, const string& call_target_name,
+ tensorflow::gtl::ArraySlice<XlaOp> operands,
+ const Shape& shape);
+ friend XlaOp HostCompute(XlaBuilder* builder,
+ tensorflow::gtl::ArraySlice<XlaOp> operands,
+ const string& channel_name, int64 cost_estimate_ns,
+ const Shape& shape);
+ friend XlaOp Complex(const XlaOp& real, const XlaOp& imag,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Conj(const XlaOp& operand);
+ friend XlaOp Add(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Sub(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Mul(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Div(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Rem(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Max(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Min(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp And(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Or(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Xor(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Not(const XlaOp& operand);
+ friend XlaOp ShiftLeft(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp ShiftRightArithmetic(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp ShiftRightLogical(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Reduce(const XlaOp& operand, const XlaOp& init_value,
+ const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<int64> dimensions_to_reduce);
+ friend XlaOp ReduceAll(const XlaOp& operand, const XlaOp& init_value,
+ const XlaComputation& computation);
+ friend XlaOp ReduceWindow(
+ const XlaOp& operand, const XlaOp& init_value,
+ const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<int64> window_dimensions,
+ tensorflow::gtl::ArraySlice<int64> window_strides, Padding padding);
+ friend XlaOp ReduceWindowWithGeneralPadding(
+ const XlaOp& operand, const XlaOp& init_value,
+ const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<int64> window_dimensions,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding);
+ friend XlaOp CrossReplicaSum(
+ const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> replica_group_ids);
+ friend XlaOp CrossReplicaSum(
+ const XlaOp& operand, const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<int64> replica_group_ids,
+ const tensorflow::gtl::optional<ChannelHandle>& channel_id);
+ friend XlaOp SelectAndScatter(
+ const XlaOp& operand, const XlaComputation& select,
+ tensorflow::gtl::ArraySlice<int64> window_dimensions,
+ tensorflow::gtl::ArraySlice<int64> window_strides, Padding padding,
+ const XlaOp& source, const XlaOp& init_value,
+ const XlaComputation& scatter);
+ friend XlaOp SelectAndScatterWithGeneralPadding(
+ const XlaOp& operand, const XlaComputation& select,
+ tensorflow::gtl::ArraySlice<int64> window_dimensions,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
+ const XlaOp& source, const XlaOp& init_value,
+ const XlaComputation& scatter);
+ friend XlaOp Abs(const XlaOp& operand);
+ friend XlaOp Atan2(const XlaOp& y, const XlaOp& x,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp Exp(const XlaOp& operand);
+ friend XlaOp Expm1(const XlaOp& operand);
+ friend XlaOp Floor(const XlaOp& operand);
+ friend XlaOp Ceil(const XlaOp& operand);
+ friend XlaOp Round(const XlaOp& operand);
+ friend XlaOp Log(const XlaOp& operand);
+ friend XlaOp Log1p(const XlaOp& operand);
+ friend XlaOp Sign(const XlaOp& operand);
+ friend XlaOp Clz(const XlaOp& operand);
+ friend XlaOp Cos(const XlaOp& operand);
+ friend XlaOp Sin(const XlaOp& operand);
+ friend XlaOp Tanh(const XlaOp& operand);
+ friend XlaOp Real(const XlaOp& operand);
+ friend XlaOp Imag(const XlaOp& operand);
+ friend XlaOp Pow(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+ friend XlaOp IsFinite(const XlaOp& operand);
+ // TODO(b/64798317): Finish CPU & GPU implementation, then replace xla::Iota
+ // in xla/client/lib/numeric.h with this (renamed to xla::Iota).
+ friend XlaOp IotaGen(XlaBuilder* builder, PrimitiveType type, int64 size);
+ friend XlaOp ConvertElementType(const XlaOp& operand,
+ PrimitiveType new_element_type);
+ friend XlaOp BitcastConvertType(const XlaOp& operand,
+ PrimitiveType new_element_type);
+ friend XlaOp Neg(const XlaOp& operand);
+ friend XlaOp Transpose(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> permutation);
+ friend XlaOp Rev(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> dimensions);
+ friend XlaOp Sort(XlaOp keys, tensorflow::gtl::optional<XlaOp> values,
+ int64 dimension);
+ friend XlaOp Clamp(const XlaOp& min, const XlaOp& operand, const XlaOp& max);
+ friend XlaOp Map(XlaBuilder* builder,
+ tensorflow::gtl::ArraySlice<XlaOp> operands,
+ const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<int64> dimensions,
+ tensorflow::gtl::ArraySlice<XlaOp> static_operands);
+ friend XlaOp RngNormal(const XlaOp& mu, const XlaOp& sigma,
+ const Shape& shape);
+ friend XlaOp RngUniform(const XlaOp& a, const XlaOp& b, const Shape& shape);
+ friend XlaOp While(const XlaComputation& condition,
+ const XlaComputation& body, const XlaOp& init);
+ friend XlaOp Conditional(const XlaOp& predicate, const XlaOp& true_operand,
+ const XlaComputation& true_computation,
+ const XlaOp& false_operand,
+ const XlaComputation& false_computation);
+ friend XlaOp ReducePrecision(const XlaOp& operand, const int exponent_bits,
+ const int mantissa_bits);
+ friend XlaOp Gather(const XlaOp& input, const XlaOp& gather_indices,
+ const GatherDimensionNumbers& dimension_numbers,
+ tensorflow::gtl::ArraySlice<int64> window_bounds);
+ friend void Send(const XlaOp& operand, const ChannelHandle& handle);
+ friend XlaOp Recv(XlaBuilder* builder, const Shape& shape,
+ const ChannelHandle& handle);
+ friend XlaOp BatchNormTraining(const XlaOp& operand, const XlaOp& scale,
+ const XlaOp& offset, float epsilon,
+ int64 feature_index);
+ friend XlaOp BatchNormInference(const XlaOp& operand, const XlaOp& scale,
+ const XlaOp& offset, const XlaOp& mean,
+ const XlaOp& variance, float epsilon,
+ int64 feature_index);
+ friend XlaOp BatchNormGrad(const XlaOp& operand, const XlaOp& scale,
+ const XlaOp& batch_mean, const XlaOp& batch_var,
+ const XlaOp& grad_output, float epsilon,
+ int64 feature_index);
+ friend XlaOp SendWithToken(const XlaOp& operand, const XlaOp& token,
+ const ChannelHandle& handle);
+ friend XlaOp RecvWithToken(const XlaOp& token, const Shape& shape,
+ const ChannelHandle& handle);
+ friend XlaOp SendToHost(const XlaOp& operand, const XlaOp& token,
+ const Shape& shape_with_layout,
+ const ChannelHandle& handle);
+ friend XlaOp RecvFromHost(const XlaOp& token, const Shape& shape,
+ const ChannelHandle& handle);
+ friend XlaOp InfeedWithToken(const XlaOp& token, const Shape& shape,
+ const string& config);
+ friend XlaOp OutfeedWithToken(const XlaOp& operand, const XlaOp& token,
+ const Shape& shape_with_layout,
+ const string& outfeed_config);
+ friend XlaOp CreateToken(XlaBuilder* builder);
+ friend XlaOp AfterAll(XlaBuilder* builder,
+ tensorflow::gtl::ArraySlice<XlaOp> tokens);
+};
+
+// RAII-style object: sets the current sharding assignment in builder on
+// construction, and sets back to the previous assignment on destruction.
+class XlaScopedShardingAssignment {
+ public:
+ XlaScopedShardingAssignment(xla::XlaBuilder* builder,
+ tensorflow::gtl::optional<OpSharding> sharding)
+ : builder_(builder), prev_sharding_(builder->sharding()) {
+ SetSharding(sharding);
+ }
+
+ XlaScopedShardingAssignment(const XlaScopedShardingAssignment&) = delete;
+ XlaScopedShardingAssignment& operator=(const XlaScopedShardingAssignment&) =
+ delete;
+
+ ~XlaScopedShardingAssignment() { SetSharding(prev_sharding_); }
+
+ private:
+ void SetSharding(const tensorflow::gtl::optional<OpSharding>& sharding) {
+ if (sharding.has_value()) {
+ builder_->SetSharding(sharding.value());
+ } else {
+ builder_->ClearSharding();
+ }
+ }
+
+ xla::XlaBuilder* const builder_;
+ tensorflow::gtl::optional<OpSharding> prev_sharding_;
+};
+
+// Free functions for building XlaOps. The intention is that these will
+// become the public API for building XlaOps rather than calling methods on
+// XlaBuilder directly.
+
+// Enqueues a "retrieve parameter value" instruction for a parameter that was
+// passed to the computation.
+XlaOp Parameter(XlaBuilder* builder, int64 parameter_number, const Shape& shape,
+ const string& name);
+
+// Enqueues a constant with the value of the given literal onto the
+// computation.
+XlaOp ConstantLiteral(XlaBuilder* builder, const LiteralSlice& literal);
+
+// Enqueues a constant onto the computation. Methods are templated on the
+// native host type (NativeT) which corresponds to a specific XLA
+// PrimitiveType as given in the following table:
+//
+// Native Type PrimitiveType
+// -----------------------------
+// bool PRED
+// int32 S32
+// int64 S64
+// uint32 U32
+// uint64 U64
+// float F32
+// double F64
+//
+// Note: not all primitive types defined in xla_data.proto have a
+// corresponding native type yet.
+template <typename NativeT>
+XlaOp ConstantR0(XlaBuilder* builder, NativeT value);
+template <typename NativeT>
+XlaOp ConstantR1(XlaBuilder* builder,
+ tensorflow::gtl::ArraySlice<NativeT> values);
+XlaOp ConstantR1(XlaBuilder* builder, const tensorflow::core::Bitmap& values);
+template <typename NativeT>
+XlaOp ConstantR2(XlaBuilder* builder,
+ std::initializer_list<std::initializer_list<NativeT>> values);
+template <typename NativeT>
+XlaOp ConstantFromArrayWithLayout(XlaBuilder* builder,
+ const Array<NativeT>& values,
+ const Layout& layout);
+template <typename NativeT>
+XlaOp ConstantFromArray(XlaBuilder* builder, const Array<NativeT>& values);
+template <typename NativeT>
+XlaOp ConstantR2FromArray2DWithLayout(XlaBuilder* builder,
+ const Array2D<NativeT>& values,
+ const Layout& layout);
+template <typename NativeT>
+XlaOp ConstantR2FromArray2D(XlaBuilder* builder,
+ const Array2D<NativeT>& values);
+template <typename NativeT>
+XlaOp ConstantR3FromArray3DWithLayout(XlaBuilder* builder,
+ const Array3D<NativeT>& values,
+ const Layout& layout);
+template <typename NativeT>
+XlaOp ConstantR3FromArray3D(XlaBuilder* builder,
+ const Array3D<NativeT>& values);
+template <typename NativeT>
+XlaOp ConstantR4FromArray4DWithLayout(XlaBuilder* builder,
+ const Array4D<NativeT>& values,
+ const Layout& layout);
+template <typename NativeT>
+XlaOp ConstantR4FromArray4D(XlaBuilder* builder,
+ const Array4D<NativeT>& values);
+
+// Enqueues a rank one constant (XlaBuilder* builder, vector) onto the
+// computation. The vector has size 'length' and every element has the value
+// 'value'.
+template <typename NativeT>
+XlaOp ConstantR1(XlaBuilder* builder, int64 length, NativeT value);
+
+// Adds dimensions to an array by duplicating the data in the array.
+//
+// The new dimensions are inserted on the left, i.e. if
+// broadcast_sizes has values {a0, ..., aN} and the operand shape
+// has dimensions {b0, ..., bM} then the shape of the output has
+// dimensions {a0, ..., aN, b0, ..., bM}.
+//
+// The new dimensions index into copies of the operand, i.e.
+//
+// output[i0, ..., iN, j0, ..., jM] = operand[j0, ..., jM]
+XlaOp Broadcast(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> broadcast_sizes);
+
+// Performs in-dimension-style broadcast.
+//
+// Operand specifies the input to be broadcast. "shape" is expected output
+// shape. "broadcast_dimensions" are the dimensions to be broadcasting into.
+// Dimension numbers in broadcast_dimensions map to individual dimensions
+// of the operand, and specify what dimension of the output shape they
+// should be broadcast.
+// e.g.
+// Say operand = [1, 2], i.e., a 1D tensor with 2 elements.
+// and dimension of shape is [2,2].
+// Specifying {1} as brodcast_dimension will generate output
+// [1 , 2]
+// [1 , 2]
+// On the other hand, specifying {0} as broadcast_dimension
+// will generate output
+// [1 , 1]
+// [2 , 2]
+XlaOp BroadcastInDim(
+ const XlaOp& operand, const Shape& shape,
+ const tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
+
+// Enqueues a pad operation onto the computation that pads the given value on
+// the edges as well as between the elements of the input. padding_config
+// specifies the padding amount for each dimension.
+XlaOp Pad(const XlaOp& operand, const XlaOp& padding_value,
+ const PaddingConfig& padding_config);
+
+// Enqueues an operation onto the computation that flattens the operand based
+// on the dimension order (major/slowest-varying to minor/fastest-varying)
+// given, followed by reshaping it into the shape with the given dimension
+// sizes (also major to minor). Conceptually, this is a limited form of
+// "shape casting".
+XlaOp Reshape(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> dimensions,
+ tensorflow::gtl::ArraySlice<int64> new_sizes);
+
+// Enqueues an operation onto the computation that collapses the operand, from
+// first to last dimension (C order), then reshapes it to the given dimension
+// sizes. Conceptually, this is a limited form of "shape casting".
+XlaOp Reshape(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> new_sizes);
+
+// Wrapper for Reshape.
+// Enqueues an operation to collapse the provided dimensions; e.g. an
+// operand with dimensions {x=256, y=2, z=2, p=32} can be collapsed to
+// {x=1024, y=32} by collapsing dims {0, 1, 2}. Collapsing dimensions must
+// be a consecutive, in-order subsequence of the operand dimensions.
+//
+// Note that collapsing a single dimension does nothing:
+//
+// {256} collapsing {0} => {256}
+// {1} collapsing {0} => {1}
+//
+// Collapsing multiple dimensions produces a single result dimension:
+//
+// {256, 2} collapsing {0,1} => {512}
+// {256, 2, 3} collapsing {0,1} => {512, 3}
+//
+// This could potentially cause data to be moved -- it provides a more
+// structured form of reshaping than an arbitrary Reshape operation.
+XlaOp Collapse(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> dimensions);
+
+// Enqueues a slice operation onto the computation that slices the operand
+// from the start indices to the limit indices; e.g.
+//
+// x
+// [ 0 1 2 3 ]
+// y [ 4 5 6 7 ] => slice(start={1, 1}, limit={2, 3}) => [ 5 6 ]
+// [ 8 9 a b ]
+//
+// Note that "limit" means up-to-but-not-including; i.e. [start, limit) in 1D
+// range notation.
+// The strides parameter determines the stride over the slice
+XlaOp Slice(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> start_indices,
+ tensorflow::gtl::ArraySlice<int64> limit_indices,
+ tensorflow::gtl::ArraySlice<int64> strides);
+
+// Enqueues a slice operation in a given dimension, taking all other
+// dimensions as they are; e.g. if dimno is 1 from start_index 2 to
+// limit_index 4 by 1, and the shape is f32[7,8,9], this call is short-hand
+// for:
+//
+// array[:, 2:4:1, :]
+XlaOp SliceInDim(const XlaOp& operand, int64 start_index, int64 limit_index,
+ int64 stride, int64 dimno);
+
+// Enqueues a slice operation onto the computation that slices the 'operand'
+// from dynamic start indices which are passed in 'start_indices'.
+// The size of the slice in each dimension is passed in 'slice_sizes',
+// which specify the end point of exclusive slice intervals in each
+// dimension [start, start + size).
+// The shape of 'start_indices' must be rank == 1, with dimension size
+// equal to the rank of the 'operand'.
+// Slice index calculations are computed modulo input dimension sizes to
+// prevent dynamic start indices from generating out-of-bound array accesses.
+XlaOp DynamicSlice(const XlaOp& operand, const XlaOp& start_indices,
+ tensorflow::gtl::ArraySlice<int64> slice_sizes);
+
+// Enqueues a dynamic update slice operation onto the computation, which
+// updates a slice of 'operand' with 'update' at dynamic 'start_indices'.
+// The shape of 'update' determines the shape of the slice of 'operand'
+// which is updated.
+// The indices specified in 'start_indices' specify the offset of the slice
+// of 'operand' which is updated.
+//
+// update = {10, 11} // calculated at runtime.
+// [1 2 3] start = {1, 1} // calculated at runtime. [1 2 3 ]
+// [4 5 6] => DynamicUpdateslice(data, update, start) => [4 10 11]
+// [7 8 9] [7 8 9 ]
+//
+// The shape of 'start_indices' must be rank == 1, with dimension size
+// equal to the rank of the 'operand'.
+// Slice index calculations are computed modulo update dimension sizes to
+// prevent dynamic start indices from generating out-of-bound array accesses.
+XlaOp DynamicUpdateSlice(const XlaOp& operand, const XlaOp& update,
+ const XlaOp& start_indices);
+
+// Enqueues a concatenate instruction onto the computation. 'operands' must
+// have >= 1 entry.
+XlaOp ConcatInDim(XlaBuilder* builder,
+ tensorflow::gtl::ArraySlice<XlaOp> operands, int64 dimension);
+
+// Enqueue a tracing operation onto the computation; the computation will emit
+// a logging message with the operand.
+void Trace(const string& tag, const XlaOp& operand);
+
+// Enqueues a conditional-move-like select operation onto the computation;
+// predicated on pred, selects between on_true and on_false.
+XlaOp Select(const XlaOp& pred, const XlaOp& on_true, const XlaOp& on_false);
+
+// Enqueues a tuple-creation instruction onto the computation.
+XlaOp Tuple(XlaBuilder* builder, tensorflow::gtl::ArraySlice<XlaOp> elements);
+
+// Enqueues a tuple-element-get instruction onto the computation.
+XlaOp GetTupleElement(const XlaOp& tuple_data, int64 index);
+
+// Enqueues an equal-to comparison instruction onto the computation.
+XlaOp Eq(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+// Enqueues a not-equal comparison instruction onto the computation.
+XlaOp Ne(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+// Enqueues a greater-or-equal comparison instruction onto the computation.
+XlaOp Ge(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+// Enqueues a greater-than comparison instruction onto the computation.
+XlaOp Gt(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+// Enqueues a less-than comparison instruction onto the computation.
+XlaOp Lt(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+// Enqueues a less-or-equal comparison instruction onto the computation.
+XlaOp Le(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+// Enqueues a dot instruction onto the computation.
+XlaOp Dot(const XlaOp& lhs, const XlaOp& rhs);
+
+// Enqueues a general dot instruction onto the computation.
+XlaOp DotGeneral(const XlaOp& lhs, const XlaOp& rhs,
+ const DotDimensionNumbers& dimension_numbers);
+
+// Enqueues a convolution instruction onto the computation, which uses the
+// default convolution dimension numbers.
+XlaOp Conv(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> window_strides, Padding padding);
+
+// Enqueues a convolution instruction onto the computation, with the caller
+// provided padding configuration in the format returned by MakePadding().
+XlaOp ConvWithGeneralPadding(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding);
+
+// Enqueues a convolution instruction onto the computation, with the caller
+// provided dimension numbers configuration.
+XlaOp ConvWithGeneralDimensions(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> window_strides, Padding padding,
+ const ConvolutionDimensionNumbers& dimension_numbers);
+
+// Enqueues a convolution instruction onto the computation, with the caller
+// provided padding configuration as well as the dimension numbers.
+XlaOp ConvGeneral(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
+ const ConvolutionDimensionNumbers& dimension_numbers);
+
+// Enqueues a convolution instruction onto the computation, with the caller
+// provided padding configuration, dilation factors and dimension numbers.
+XlaOp ConvGeneralDilated(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
+ tensorflow::gtl::ArraySlice<int64> lhs_dilation,
+ tensorflow::gtl::ArraySlice<int64> rhs_dilation,
+ const ConvolutionDimensionNumbers& dimension_numbers);
+
+// Enqueues an FFT instruction onto the computation, of the given type and
+// with the given FFT length.
+XlaOp Fft(const XlaOp& operand, FftType fft_type,
+ tensorflow::gtl::ArraySlice<int64> fft_length);
+
+// Enqueues an infeed instruction onto the computation, which writes data of
+// the given shape to the infeed buffer of the device.
+XlaOp Infeed(XlaBuilder* builder, const Shape& shape,
+ const string& config = "");
+
+// Variant of Infeed which takes a token-shaped operand and produces a
+// two-element tuple containing the data value and a token-shaped value.
+// Tokens are used for ordering side-effecting operations.
+// TODO(b/110532604): Replace all uses of the non-token form with this variant.
+XlaOp InfeedWithToken(const XlaOp& token, const Shape& shape,
+ const string& config = "");
+
+// Enqueues an outfeed instruction onto the computation. This instruction
+// generates outgoing data transfers for the given data.
+//
+// shape_with_layout communicates the laid out shape that we want to outfeed
+// -- if !ShapeUtil::Compatible(GetShape(operand), shape_with_layout) an error
+// will occur.
+void Outfeed(const XlaOp& operand, const Shape& shape_with_layout,
+ const string& outfeed_config);
+
+// Variant of Outfeed which takes a token-shaped operand and produces a
+// token-shaped value. Tokens are used for ordering side-effecting operations.
+// TODO(b/110532604): Replace all uses of the non-token form with this variant.
+XlaOp OutfeedWithToken(const XlaOp& operand, const XlaOp& token,
+ const Shape& shape_with_layout,
+ const string& outfeed_config);
+
+// Enqueues a call instruction onto the computation.
+XlaOp Call(XlaBuilder* builder, const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<XlaOp> operands);
+
+// Enqueues a custom call instruction onto the computation.
+// During code generation, a call instruction is emitted which targets a
+// symbol with the name |call_target_name|. The |operands| are passed to the
+// call instruction. |shape| is the resultant shape.
+XlaOp CustomCall(XlaBuilder* builder, const string& call_target_name,
+ tensorflow::gtl::ArraySlice<XlaOp> operands,
+ const Shape& shape);
+
+// Enqueues a pseudo-op to represent host-side computation data-dependencies.
+// During code generation, host send and receive operations will be generated
+// to transfer |operands| to the host and a single result of |shape| back to
+// the device. Host send/recv operations are emitted using |channel_name|.
+// Dataflow dependencies and the |cost_estimate_ns| field may be used in HLO
+// instruction scheduling.
+XlaOp HostCompute(XlaBuilder* builder,
+ tensorflow::gtl::ArraySlice<XlaOp> operands,
+ const string& channel_name, int64 cost_estimate_ns,
+ const Shape& shape);
+
+// The following methods enqueue element-wise binary arithmetic operations
+// onto the computation. The shapes of the operands have to match unless one
+// of the operands is a scalar, or an explicit broadcast dimension is given
+// (see g3doc for more details).
+
+// Enqueues a complex compose instruction onto the computation.
+XlaOp Complex(const XlaOp& real, const XlaOp& imag,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+// Enqueues a complex conjugate instruction onto the computation.
+XlaOp Conj(const XlaOp& operand);
+
+// Enqueues an add instruction onto the computation.
+XlaOp Add(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+// Enqueues a subtract instruction onto the computation.
+XlaOp Sub(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+// Enqueues a multiply instruction onto the computation.
+XlaOp Mul(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+// Enqueues a divide instruction onto the computation.
+XlaOp Div(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+// Enqueues a remainder instruction onto the computation.
+XlaOp Rem(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+// Enqueues a max instruction onto the computation.
+XlaOp Max(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+// Enqueues a min instruction onto the computation.
+XlaOp Min(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+// Element-wise logical operators
+XlaOp And(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+XlaOp Or(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+XlaOp Xor(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+XlaOp Not(const XlaOp& operand);
+
+XlaOp ShiftLeft(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+XlaOp ShiftRightArithmetic(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+XlaOp ShiftRightLogical(
+ const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+// Reduces an array among the provided dimensions, given "computation" as a
+// reduction operator.
+XlaOp Reduce(const XlaOp& operand, const XlaOp& init_value,
+ const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<int64> dimensions_to_reduce);
+
+// Convenience wrapper around the above that reduces all the dimensions in the
+// operand shape.
+XlaOp ReduceAll(const XlaOp& operand, const XlaOp& init_value,
+ const XlaComputation& computation);
+
+// Enqueues a windowed reduce instruction onto the computation.
+XlaOp ReduceWindow(const XlaOp& operand, const XlaOp& init_value,
+ const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<int64> window_dimensions,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ Padding padding);
+
+// As ReduceWindow(), but the padding is given in the format
+// returned by MakePadding().
+XlaOp ReduceWindowWithGeneralPadding(
+ const XlaOp& operand, const XlaOp& init_value,
+ const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<int64> window_dimensions,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding);
+
+// Returns the sum of the operand value within each subgroup of replicas. All
+// replicas supply one input to the sum and all replicas receive the resulting
+// sum for each subgroup.
+XlaOp CrossReplicaSum(
+ const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> replica_group_ids = {});
+
+// Enqueues an operation that do an AllReduce of the operand cross cores. Here
+// AllReduce means doing a reduction on the input operand cross cores and then
+// broadcasting the reduction result to those cores. The reduction function is
+// defined by `computation`, which should be a commutative computation on
+// scalars, e.g., add, min, or max. The way that AllReduce is applied is
+// configured by:
+//
+// - `replica_group_ids`: maps replica ids to subgroup ids. If empty, all
+// replicas belong to one group. Allreduce will be applied within subgroups.
+// For example, we have 4 replicas, then replica_group_ids={0,1,0,1} means,
+// replica 0 and 2 are in subgroup 0, replica 1 and 3 are in subgroup 1.
+//
+// - `channel_id`: for Allreduce nodes from different models, if they have the
+// same channel_id, they will be 'Allreduce'd. If empty, Allreduce will not be
+// applied cross models.
+//
+// TODO(b/79737069): Rename this to AllReduce when it's ready to use.
+XlaOp CrossReplicaSum(const XlaOp& operand, const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<int64> replica_group_ids = {},
+ const tensorflow::gtl::optional<ChannelHandle>&
+ channel_id = tensorflow::gtl::nullopt);
+
+// Enqueues an operation that scatters the `source` array to the selected
+// indices of each window.
+XlaOp SelectAndScatter(const XlaOp& operand, const XlaComputation& select,
+ tensorflow::gtl::ArraySlice<int64> window_dimensions,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ Padding padding, const XlaOp& source,
+ const XlaOp& init_value, const XlaComputation& scatter);
+
+// As SelectAndScatter(), but the padding is given in the format
+// returned by MakePadding().
+XlaOp SelectAndScatterWithGeneralPadding(
+ const XlaOp& operand, const XlaComputation& select,
+ tensorflow::gtl::ArraySlice<int64> window_dimensions,
+ tensorflow::gtl::ArraySlice<int64> window_strides,
+ tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
+ const XlaOp& source, const XlaOp& init_value,
+ const XlaComputation& scatter);
+
+// Enqueues an abs instruction onto the computation.
+XlaOp Abs(const XlaOp& operand);
+
+// Enqueues a atan2 instruction onto the computation.
+XlaOp Atan2(const XlaOp& y, const XlaOp& x,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+// Enqueues an exp instruction onto the computation.
+XlaOp Exp(const XlaOp& operand);
+
+// Enqueues an expm1 instruction onto the computation.
+XlaOp Expm1(const XlaOp& operand);
+
+// Enqueues a floor instruction onto the computation.
+XlaOp Floor(const XlaOp& operand);
+
+// Enqueues a ceil instruction onto the computation.
+XlaOp Ceil(const XlaOp& operand);
+
+// Enqueues a round instruction onto the computation, rounding to nearest even
+// with half-way cases rounding away from zero.
+XlaOp Round(const XlaOp& operand);
+
+// Enqueues an log instruction (natural logarithm) onto the computation.
+XlaOp Log(const XlaOp& operand);
+
+// Enqueues an log1p instruction (log(x+1)) onto the computation.
+XlaOp Log1p(const XlaOp& operand);
+
+// Enqueues a sign instruction onto the computation.
+XlaOp Sign(const XlaOp& operand);
+
+// Enqueues a count leading zeros instruction onto the computation.
+XlaOp Clz(const XlaOp& operand);
+
+// Enqueues a cosine instruction onto the computation.
+XlaOp Cos(const XlaOp& operand);
+
+// Enqueues a sine instruction onto the computation.
+XlaOp Sin(const XlaOp& operand);
+
+// Enqueues a tanh instruction onto the computation.
+XlaOp Tanh(const XlaOp& operand);
+
+// Enqueues a real-part instruction onto the computation.
+XlaOp Real(const XlaOp& operand);
+
+// Enqueues an imaginary-part instruction onto the computation.
+XlaOp Imag(const XlaOp& operand);
+
+// Enqueues a lhs^rhs computation onto the computation.
+XlaOp Pow(const XlaOp& lhs, const XlaOp& rhs,
+ tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
+
+// Enqueues an operator that tests if the operand's values are finite, i.e.,
+// not Inf or NaN. Defined only for floating-point types. Returns an array of
+// booleans with the same shape where entries are true iff the corresponding
+// entry was NaN.
+XlaOp IsFinite(const XlaOp& operand);
+
+// Enqueues a convert instruction onto the computation that changes the
+// element type of the operand array to primitive_type.
+XlaOp ConvertElementType(const XlaOp& operand, PrimitiveType new_element_type);
+
+// Enqueues a no-op instruction onto the computation that changes
+// the element type of the operand array to primitive_type. The
+// bit-widths of the source and destination element types must be
+// identical.
+XlaOp BitcastConvertType(const XlaOp& operand, PrimitiveType new_element_type);
+
+// Enqueues a negate instruction onto the computation.
+XlaOp Neg(const XlaOp& operand);
+
+// Enqueues a transpose instruction onto the computation.
+XlaOp Transpose(const XlaOp& operand,
+ tensorflow::gtl::ArraySlice<int64> permutation);
+
+// Enqueues a reverse instruction onto the computation. The order of the
+// elements in the given dimensions is reversed (i.e., the element at index i
+// is moved to index dimension_size - 1 - i).
+XlaOp Rev(const XlaOp& operand, tensorflow::gtl::ArraySlice<int64> dimensions);
+
+// Enqueues a sort (as increasing order) instruction onto the computation.
+// If only keys are provided:
+// * If the keys are an rank-1 tensor (an array), the result is a sorted array
+// of keys, in ascending order.
+// * If the keys have higher rank, the keys are sorted along the provided
+// dimension. For example, for a rank-2 tensor (a matrix) of keys, a dimension
+// value of 0 will indepenently sort every column, and a dimension value of 1
+// will independently sort each row. If no dimension number is provided, then
+// the last dimension is chosen by default.
+//
+// If both keys and values are provided:
+// * The keys and the values must tensors with the same dimensions. The
+// element types of the tensors may be different.
+// * The result is a tuple that consists of a sorted tensor of keys (along the
+// provided dimension, as above) as the first element, and a tensor with their
+// corresponding values as the second element.
+XlaOp Sort(XlaOp keys,
+ tensorflow::gtl::optional<XlaOp> values = tensorflow::gtl::nullopt,
+ int64 dimension = -1);
+
+// Enqueues a clamp instruction onto the computation.
+XlaOp Clamp(const XlaOp& min, const XlaOp& operand, const XlaOp& max);
+
+// Enqueues a map instruction onto the computation.
+XlaOp Map(XlaBuilder* builder, tensorflow::gtl::ArraySlice<XlaOp> operands,
+ const XlaComputation& computation,
+ tensorflow::gtl::ArraySlice<int64> dimensions,
+ tensorflow::gtl::ArraySlice<XlaOp> static_operands = {});
+
+// Enqueues a N(mu, sigma) random number generation instruction onto the
+// computation.
+XlaOp RngNormal(const XlaOp& mu, const XlaOp& sigma, const Shape& shape);
+
+// Enqueues a U(a, b) random number generation instruction onto the
+// computation. Returns values in the semi-open interval [a, b).
+XlaOp RngUniform(const XlaOp& a, const XlaOp& b, const Shape& shape);
+
+// Enqueues a while node onto the computation.
+XlaOp While(const XlaComputation& condition, const XlaComputation& body,
+ const XlaOp& init);
+
+// Enqueues a conditional node onto the computation.
+XlaOp Conditional(const XlaOp& predicate, const XlaOp& true_operand,
+ const XlaComputation& true_computation,
+ const XlaOp& false_operand,
+ const XlaComputation& false_computation);
+
+// Enqueues a ReducePrecision node onto the computation.
+XlaOp ReducePrecision(const XlaOp& operand, const int exponent_bits,
+ const int mantissa_bits);
+
+// Enqueues a Gather node onto the computation.
+XlaOp Gather(const XlaOp& input, const XlaOp& gather_indices,
+ const GatherDimensionNumbers& dimension_numbers,
+ tensorflow::gtl::ArraySlice<int64> window_bounds);
+
+// Enqueues a Send node onto the computation for device-to-device
+// communication. This operation sends the given operand to
+// a Recv instruction in a different computation that shares the same channel
+// handle.
+void Send(const XlaOp& operand, const ChannelHandle& handle);
+
+// Variant of Send which takes a token-shaped operand and produces a
+// token-shaped value. Tokens are used for ordering side-effecting operations.
+// TODO(b/110532604): Replace all uses of the non-token form with this variant.
+XlaOp SendWithToken(const XlaOp& operand, const XlaOp& token,
+ const ChannelHandle& handle);
+
+// Enqueues a Recv node onto the computation for device-to-device
+// communication. The data comes from a Send instruction in a different
+// computation that shares the same channel handle and its shape must be the
+// same as the given shape.
+XlaOp Recv(XlaBuilder* builder, const Shape& shape,
+ const ChannelHandle& handle);
+
+// Variant of Recv which takes a token-shaped operand and produces a two-element
+// tuple containing the data value and a token-shaped value. Tokens are used
+// for ordering side-effecting operations.
+// TODO(b/110532604): Replace all uses of the non-token form with this variant.
+XlaOp RecvWithToken(const XlaOp& token, const Shape& shape,
+ const ChannelHandle& handle);
+
+// Enqueues a Send node which transfers data from the device to the host. The
+// 'shape_with_layout' argument defines the layout of the data transferred; its
+// shape must be compatible with the shape of the operand. The operand must be
+// array-shaped.
+// TODO(b/111544877): Support tuple shapes.
+XlaOp SendToHost(const XlaOp& operand, const XlaOp& token,
+ const Shape& shape_with_layout, const ChannelHandle& handle);
+
+// Enqueues a Recv node which transfers data from the host to the device. The
+// given shape must contain a layout and must be an array.
+// TODO(b/111544877): Support tuple shapes.
+XlaOp RecvFromHost(const XlaOp& token, const Shape& shape,
+ const ChannelHandle& handle);
+
+// Enqueues an operation (AfterAll) with no operands that produces a
+// token-shaped value. Tokens are used for ordering side-effecting operations.
+// This is a separate method from AfterAll to facility the removal of
+// operand-less AfterAll instructions.
+// TODO(b/110532604): Remove this function when all tokens are derived from a
+// single token generated or passed into the entry computation.
+XlaOp CreateToken(XlaBuilder* builder);
+
+// Enqueues an AfterAll instruction which produces a token-shaped value and
+// takes a variadic number of token-shaped operands. The number of operands must
+// be greater than zero. Used for joining tokens.
+XlaOp AfterAll(XlaBuilder* builder, tensorflow::gtl::ArraySlice<XlaOp> tokens);
+
+// Normalizes operand across spatial and batch dimensions for each feature.
+//
+// Returns a tuple (normalized, batch_mean, batch_var) where `normalized`
+// is the normalized result and batch_mean and batch_var are the mean and
+// variance, respectively, across batch for the operand.
+XlaOp BatchNormTraining(const XlaOp& operand, const XlaOp& scale,
+ const XlaOp& offset, float epsilon,
+ int64 feature_index);
+
+// Normalizes operand across spatial and batch dimensions for each feature.
+//
+// `BatchNormInference` is equivalent to calling `BatchNormTraining` without
+// computing `mean` and `variance` for each batch inside the operation. It
+// uses the input `mean` and `variance` instead as estimated values. The
+// purpose of this op is to reduce latency in inference, hence the name
+// `BatchNormInference`.
+//
+// The output has the same shape as `operand`, and contains the normalized
+// values for each batch.
+XlaOp BatchNormInference(const XlaOp& operand, const XlaOp& scale,
+ const XlaOp& offset, const XlaOp& mean,
+ const XlaOp& variance, float epsilon,
+ int64 feature_index);
+
+// Calculates the gradients of a batch norm op.
+//
+// The inputs `batch_mean` and `batch_var` represent the mean and variance
+// across the batch.
+//
+// Returns a tuple of three elements:
+// - grad_operand: Gradient with respect to input `operand`
+// - grad_offset: Gradient with respect to input `offset`
+// - grad_scale: Gradient with respect to input `scale`
+XlaOp BatchNormGrad(const XlaOp& operand, const XlaOp& scale,
+ const XlaOp& batch_mean, const XlaOp& batch_var,
+ const XlaOp& grad_output, float epsilon,
+ int64 feature_index);
+
+// Implementation details below this point.
+
+template <typename NativeT>
+XlaOp XlaBuilder::ConstantR0(NativeT value) {
+ return ConstantLiteral(*LiteralUtil::CreateR0<NativeT>(value));
+}
+
+template <typename NativeT>
+XlaOp XlaBuilder::ConstantR1(tensorflow::gtl::ArraySlice<NativeT> values) {
+ return ConstantLiteral(*LiteralUtil::CreateR1<NativeT>(values));
+}
+
+template <typename NativeT>
+XlaOp XlaBuilder::ConstantR1(int64 length, NativeT value) {
+ Literal literal(ShapeUtil::MakeShape(
+ primitive_util::NativeToPrimitiveType<NativeT>(), {length}));
+ literal.PopulateWithValue(value);
+ return ConstantLiteral(literal);
+}
+
+inline XlaOp XlaBuilder::ConstantR1(const tensorflow::core::Bitmap& values) {
+ return ConstantLiteral(*LiteralUtil::CreateR1(values));
+}
+
+template <typename NativeT>
+XlaOp XlaBuilder::ConstantR2(
+ std::initializer_list<std::initializer_list<NativeT>> values) {
+ return ConstantLiteral(*LiteralUtil::CreateR2<NativeT>(values));
+}
+
+template <typename NativeT>
+XlaOp XlaBuilder::ConstantFromArrayWithLayout(const Array<NativeT>& values,
+ const Layout& layout) {
+ return ConstantLiteral(
+ *LiteralUtil::CreateFromArrayWithLayout<NativeT>(values, layout));
+}
+
+template <typename NativeT>
+XlaOp XlaBuilder::ConstantFromArray(const Array<NativeT>& values) {
+ return ConstantLiteral(*LiteralUtil::CreateFromArray<NativeT>(values));
+}
+
+template <typename NativeT>
+XlaOp XlaBuilder::ConstantR2FromArray2DWithLayout(
+ const Array2D<NativeT>& values, const Layout& layout) {
+ return ConstantLiteral(
+ *LiteralUtil::CreateFromArrayWithLayout<NativeT>(values, layout));
+}
+
+template <typename NativeT>
+XlaOp XlaBuilder::ConstantR2FromArray2D(const Array2D<NativeT>& values) {
+ return ConstantLiteral(*LiteralUtil::CreateR2FromArray2D<NativeT>(values));
+}
+
+template <typename NativeT>
+XlaOp XlaBuilder::ConstantR3FromArray3DWithLayout(
+ const Array3D<NativeT>& values, const Layout& layout) {
+ return ConstantLiteral(
+ *LiteralUtil::CreateR3FromArray3DWithLayout<NativeT>(values, layout));
+}
+
+template <typename NativeT>
+XlaOp XlaBuilder::ConstantR3FromArray3D(const Array3D<NativeT>& values) {
+ return ConstantFromArray(values);
+}
+
+template <typename NativeT>
+XlaOp XlaBuilder::ConstantR4FromArray4DWithLayout(
+ const Array4D<NativeT>& values, const Layout& layout) {
+ return ConstantFromArrayWithLayout(values, layout);
+}
+
+template <typename NativeT>
+XlaOp XlaBuilder::ConstantR4FromArray4D(const Array4D<NativeT>& values) {
+ return ConstantFromArray(values);
+}
+
+// Free function template implementations.
+
+template <typename NativeT>
+XlaOp ConstantR0(XlaBuilder* builder, NativeT value) {
+ return ConstantLiteral(builder, *LiteralUtil::CreateR0<NativeT>(value));
+}
+
+template <typename NativeT>
+XlaOp ConstantR1(XlaBuilder* builder,
+ tensorflow::gtl::ArraySlice<NativeT> values) {
+ return ConstantLiteral(builder, *LiteralUtil::CreateR1<NativeT>(values));
+}
+
+template <typename NativeT>
+XlaOp ConstantR1(XlaBuilder* builder, int64 length, NativeT value) {
+ Literal literal(ShapeUtil::MakeShape(
+ primitive_util::NativeToPrimitiveType<NativeT>(), {length}));
+ literal.PopulateWithValue(value);
+ return ConstantLiteral(builder, literal);
+}
+
+inline XlaOp ConstantR1(XlaBuilder* builder,
+ const tensorflow::core::Bitmap& values) {
+ return ConstantLiteral(builder, *LiteralUtil::CreateR1(values));
+}
+
+template <typename NativeT>
+XlaOp ConstantR2(XlaBuilder* builder,
+ std::initializer_list<std::initializer_list<NativeT>> values) {
+ return ConstantLiteral(builder, *LiteralUtil::CreateR2<NativeT>(values));
+}
+
+template <typename NativeT>
+XlaOp ConstantFromArrayWithLayout(XlaBuilder* builder,
+ const Array<NativeT>& values,
+ const Layout& layout) {
+ return ConstantLiteral(
+ builder,
+ *LiteralUtil::CreateFromArrayWithLayout<NativeT>(values, layout));
+}
+
+template <typename NativeT>
+XlaOp ConstantFromArray(XlaBuilder* builder, const Array<NativeT>& values) {
+ return ConstantLiteral(builder,
+ *LiteralUtil::CreateFromArray<NativeT>(values));
+}
+
+template <typename NativeT>
+XlaOp ConstantR2FromArray2DWithLayout(XlaBuilder* builder,
+ const Array2D<NativeT>& values,
+ const Layout& layout) {
+ return ConstantLiteral(
+ builder,
+ *LiteralUtil::CreateFromArrayWithLayout<NativeT>(values, layout));
+}
+
+template <typename NativeT>
+XlaOp ConstantR2FromArray2D(XlaBuilder* builder,
+ const Array2D<NativeT>& values) {
+ return ConstantLiteral(builder,
+ *LiteralUtil::CreateR2FromArray2D<NativeT>(values));
+}
+
+template <typename NativeT>
+XlaOp ConstantR3FromArray3DWithLayout(XlaBuilder* builder,
+ const Array3D<NativeT>& values,
+ const Layout& layout) {
+ return ConstantLiteral(
+ builder,
+ *LiteralUtil::CreateR3FromArray3DWithLayout<NativeT>(values, layout));
+}
+
+template <typename NativeT>
+XlaOp ConstantR3FromArray3D(XlaBuilder* builder,
+ const Array3D<NativeT>& values) {
+ return ConstantFromArray(builder, values);
+}
+
+template <typename NativeT>
+XlaOp ConstantR4FromArray4DWithLayout(XlaBuilder* builder,
+ const Array4D<NativeT>& values,
+ const Layout& layout) {
+ return ConstantFromArrayWithLayout(builder, values, layout);
+}
+
+template <typename NativeT>
+XlaOp ConstantR4FromArray4D(XlaBuilder* builder,
+ const Array4D<NativeT>& values) {
+ return ConstantFromArray(builder, values);
+}
+
+} // namespace xla
+
+#endif // TENSORFLOW_COMPILER_XLA_CLIENT_XLA_BUILDER_H_
diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc b/tensorflow/compiler/xla/client/xla_builder_test.cc
index 3b8beb2c78..28a207b137 100644
--- a/tensorflow/compiler/xla/client/xla_client/xla_builder_test.cc
+++ b/tensorflow/compiler/xla/client/xla_builder_test.cc
@@ -13,10 +13,11 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include <string>
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/legacy_flags/debug_options_flags.h"
#include "tensorflow/compiler/xla/service/hlo_matchers.h"
#include "tensorflow/compiler/xla/service/hlo_module.h"
diff --git a/tensorflow/compiler/xla/client/xla_client/BUILD b/tensorflow/compiler/xla/client/xla_client/BUILD
index 763653c685..2e131dbad2 100644
--- a/tensorflow/compiler/xla/client/xla_client/BUILD
+++ b/tensorflow/compiler/xla/client/xla_client/BUILD
@@ -24,56 +24,10 @@ filegroup(
load("//tensorflow:tensorflow.bzl", "tf_cc_test")
cc_library(
- name = "xla_computation",
- srcs = ["xla_computation.cc"],
- hdrs = ["xla_computation.h"],
- deps = [
- "//tensorflow/compiler/xla:status_macros",
- "//tensorflow/compiler/xla:util",
- "//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/service:hlo_proto",
- ],
-)
-
-cc_library(
name = "xla_builder",
- srcs = ["xla_builder.cc"],
hdrs = ["xla_builder.h"],
visibility = ["//visibility:public"],
deps = [
- ":xla_computation",
- "//tensorflow/compiler/xla:execution_options_util",
- "//tensorflow/compiler/xla:literal",
- "//tensorflow/compiler/xla:literal_util",
- "//tensorflow/compiler/xla:shape_util",
- "//tensorflow/compiler/xla:status_macros",
- "//tensorflow/compiler/xla:statusor",
- "//tensorflow/compiler/xla:types",
- "//tensorflow/compiler/xla:util",
- "//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/client:padding",
- "//tensorflow/compiler/xla/client:sharding_builder",
- "//tensorflow/compiler/xla/service:hlo",
- "//tensorflow/compiler/xla/service:hlo_proto",
- "//tensorflow/compiler/xla/service:shape_inference",
- "//tensorflow/core:lib",
- ],
-)
-
-tf_cc_test(
- name = "xla_builder_test",
- srcs = ["xla_builder_test.cc"],
- deps = [
- ":xla_builder",
- "//tensorflow/compiler/xla:literal",
- "//tensorflow/compiler/xla:shape_util",
- "//tensorflow/compiler/xla:status_macros",
- "//tensorflow/compiler/xla:test",
- "//tensorflow/compiler/xla:test_helpers",
- "//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/legacy_flags:debug_options_flags",
- "//tensorflow/compiler/xla/service:hlo",
- "//tensorflow/compiler/xla/service:hlo_matchers",
- "//tensorflow/core:test",
+ "//tensorflow/compiler/xla/client:xla_builder",
],
)
diff --git a/tensorflow/compiler/xla/client/xla_client/xla_builder.h b/tensorflow/compiler/xla/client/xla_client/xla_builder.h
index 8359d936b7..ce2a8afd4c 100644
--- a/tensorflow/compiler/xla/client/xla_client/xla_builder.h
+++ b/tensorflow/compiler/xla/client/xla_client/xla_builder.h
@@ -16,2226 +16,6 @@ limitations under the License.
#ifndef TENSORFLOW_COMPILER_XLA_CLIENT_XLA_CLIENT_XLA_BUILDER_H_
#define TENSORFLOW_COMPILER_XLA_CLIENT_XLA_CLIENT_XLA_BUILDER_H_
-#include <map>
-#include <string>
-#include <type_traits>
-#include <utility>
-
-#include "tensorflow/compiler/xla/client/padding.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
-#include "tensorflow/compiler/xla/literal.h"
-#include "tensorflow/compiler/xla/literal_util.h"
-#include "tensorflow/compiler/xla/service/hlo.pb.h"
-#include "tensorflow/compiler/xla/service/hlo_opcode.h"
-#include "tensorflow/compiler/xla/shape_util.h"
-#include "tensorflow/compiler/xla/status_macros.h"
-#include "tensorflow/compiler/xla/statusor.h"
-#include "tensorflow/compiler/xla/types.h"
-#include "tensorflow/compiler/xla/xla_data.pb.h"
-#include "tensorflow/core/lib/core/stringpiece.h"
-#include "tensorflow/core/lib/gtl/array_slice.h"
-#include "tensorflow/core/lib/gtl/flatset.h"
-#include "tensorflow/core/platform/macros.h"
-#include "tensorflow/core/platform/stacktrace.h"
-#include "tensorflow/core/platform/types.h"
-
-namespace xla {
-
-class XlaBuilder;
-
-// This represents an instruction that has been enqueued using the XlaBuilder.
-// This is used to pass to subsequent computations that depends upon the
-// instruction as an operand.
-class XlaOp {
- public:
- XlaOp() : handle_(-1), builder_(nullptr) {
- static_assert(std::is_trivially_destructible<XlaOp>::value,
- "XlaOp should be trivially destructible");
- }
- ~XlaOp() = default;
-
- // Precondition: !IsUninitialized().
- //
- // It's very common to do foo.builder()->bar(). Without this precondition, if
- // foo.builder() is null, the call to bar will segfault at some point possibly
- // deep in the callstack when we finally dereference `this`. The precondition
- // lets us avoid this tricky-to-debug problem.
- XlaBuilder* builder() const {
- CHECK(builder_ != nullptr);
- return builder_;
- }
-
- // Returns true if the XlaOp represents valid, non-erroneous value.
- bool valid() const { return handle_ >= 0; }
-
- // Returns true if the XlaOp was created by the XlaOp() constructor and
- // not returned by a builder.
- bool IsUninitialized() const { return builder_ == nullptr; }
-
- bool IsIdenticalTo(const XlaOp& rhs) const {
- return handle_ == rhs.handle_ && builder_ == rhs.builder_;
- }
-
- friend std::ostream& operator<<(std::ostream& out, const XlaOp& op) {
- out << op.handle();
- return out;
- }
-
- private:
- explicit XlaOp(XlaBuilder* builder) : handle_(-1), builder_(builder) {}
- XlaOp(int64 handle, XlaBuilder* builder)
- : handle_(handle), builder_(builder) {}
-
- int64 handle() const { return handle_; }
-
- friend class XlaBuilder;
-
- // < 0 means "invalid handle".
- int64 handle_;
-
- // Not owned. Non-null for any handle returned by XlaBuilder, even if the
- // handle is invalid.
- XlaBuilder* builder_;
-};
-
-// Arithmetic operator overloads for the XlaOp type.
-XlaOp operator-(const XlaOp& x);
-XlaOp operator+(const XlaOp& x, const XlaOp& y);
-XlaOp operator-(const XlaOp& x, const XlaOp& y);
-XlaOp operator*(const XlaOp& x, const XlaOp& y);
-XlaOp operator/(const XlaOp& x, const XlaOp& y);
-XlaOp operator%(const XlaOp& x, const XlaOp& y);
-
-// Bitwise operator overloads for the XlaOp type.
-XlaOp operator~(const XlaOp& x);
-XlaOp operator&(const XlaOp& x, const XlaOp& y);
-XlaOp operator|(const XlaOp& x, const XlaOp& y);
-XlaOp operator^(const XlaOp& x, const XlaOp& y);
-XlaOp operator<<(const XlaOp& x, const XlaOp& y);
-// Performs a right arithmetic shift if 'x' is a signed type, otherwise performs
-// a right logical shift.
-XlaOp operator>>(const XlaOp& x, const XlaOp& y);
-
-// We don't overload the relational operators (==, !=, <, <=, >, >=) because the
-// semantics might be surprising since their result types are usually 'bool'.
-// Further programmers may expect == to be a structural equality.
-// We also choose not to overload any of the mutating operators (e.g., +=, -=)
-// because the semantics might be misleading — XLA computations are immutable.
-
-// A convenient interface for building up computations.
-//
-// Thread-compatible.
-class XlaBuilder {
- public:
- // computation_name: name to use for the built computation.
- XlaBuilder(const string& computation_name);
-
- XlaBuilder(const XlaBuilder&) = delete;
- XlaBuilder& operator=(const XlaBuilder&) = delete;
-
- ~XlaBuilder();
-
- // Returns the computation name.
- const string& name() const { return name_; }
-
- // Sets OpMetadata that will be added to all instructions until cleared.
- //
- // OpMetadata is often applied to a series of XLA HLO instructions. As a
- // result, OpMetadata is set on the Computation Builder. All subsequent
- // instructions generated via this Computation Builder will have the same
- // OpMetadata attached until a call to ClearOpMetadata.
- void SetOpMetadata(const OpMetadata& metadata) { metadata_ = metadata; }
-
- // Clears the HloMetadata state.
- void ClearOpMetadata() { metadata_.Clear(); }
-
- // Sets an OpSharding that will be attached to all instructions until cleared.
- void SetSharding(const OpSharding& sharding) { sharding_ = sharding; }
-
- // Clears the sharding. Ops will be sharded according to the default placement
- // policy.
- void ClearSharding() { sharding_ = tensorflow::gtl::nullopt; }
-
- // Returns the OpSharding that will be attached to all instructions.
- const tensorflow::gtl::optional<OpSharding>& sharding() const {
- return sharding_;
- }
-
- // Sets the builder to a mode where it will die immediately when an error is
- // encountered, rather than producing it in a deferred fashion when Build() is
- // called (which is the default).
- void set_die_immediately_on_error(bool enabled) {
- die_immediately_on_error_ = enabled;
- }
-
- // Default dimension numbers used for a 2D convolution.
- static constexpr int64 kConvBatchDimension = 0;
- static constexpr int64 kConvFeatureDimension = 1;
- static constexpr int64 kConvFirstSpatialDimension = 2;
- static constexpr int64 kConvSecondSpatialDimension = 3;
- static constexpr int64 kConvKernelOutputDimension = 0;
- static constexpr int64 kConvKernelInputDimension = 1;
- static constexpr int64 kConvKernelFirstSpatialDimension = 2;
- static constexpr int64 kConvKernelSecondSpatialDimension = 3;
-
- // Creates a default ConvolutionDimensionNumbers. For a 2D convolution, for
- // the input operand {batch, feature, height, width} = {0, 1, 2, 3} and for
- // the kernel operand
- // {output_feature, input_feature, height, width} = {0, 1, 2, 3}.
- static ConvolutionDimensionNumbers CreateDefaultConvDimensionNumbers(
- int num_spatial_dims = 2);
-
- // Returns an error if the convolution dimension numbers have conflicts.
- static Status Validate(const ConvolutionDimensionNumbers& dnum);
-
- // Returns a new XlaBuilder whose resultant Computation is used only by this
- // XlaBuilder. The sub-XlaBuilder has the same die_immediately_on_error
- // behavior as the parent.
- std::unique_ptr<XlaBuilder> CreateSubBuilder(const string& computation_name);
-
- // Builds the computation with the requested operations, or returns a non-ok
- // status. Note that all ops that have been enqueued will be moved to the
- // computation being returned.
- StatusOr<XlaComputation> Build();
-
- // Builds the computation with the requested operations, or notes an error in
- // the parent XlaBuilder and returns an empty computation if building failed.
- // This function is intended to be used where the returned XlaComputation is
- // only used by the parent XlaBuilder and hence further operation on the
- // returned XlaComputation will simply be error'ed out if an error occurred
- // while building this computation. If the built computation is to be used by
- // a XlaBuilder other than the parent XlaBuilder then Build() should be used
- // instead.
- XlaComputation BuildAndNoteError();
-
- // Returns a subgraph that roots on the given root. If the root is not a
- // compile-time constant (see `IsConstant`), returns an error.
- //
- // This will copy the needed ops/computations to the subgraph.
- StatusOr<XlaComputation> BuildConstantSubGraph(const XlaOp& root_op) const;
-
- // Returns the first error that was encountered while building the
- // computation. When an error is encountered, by default we return a vacuous
- // XlaOp and inform the user of the error that occurred while
- // building the computation when they make a final call to Build().
- //
- // See also set_die_immediately_on_error().
- Status first_error() const { return first_error_; }
-
- // Returns the shape of the given op.
- StatusOr<Shape> GetShape(const XlaOp& op) const;
-
- // Returns the (inferred) result for the current computation's shape.
- StatusOr<ProgramShape> GetProgramShape() const;
-
- // Reports an error to the builder, by
- // * storing it internally and capturing a backtrace if it's the first error
- // (this deferred value will be produced on the call to
- // Build()/GetShape()/...)
- // * dying if die_immediately_on_error_ is true.
- // Returns an XlaOp with an invalid handle but a valid builder. This value can
- // be returned in place of a value in APIs that return an XlaOp.
- XlaOp ReportError(const Status& error);
-
- // A helper function that converts a StatusOr<XlaOp> into an XlaOp.
- // If the Status was an error, reports the error to builder and returns an
- // invalid XlaOp handle.
- XlaOp ReportErrorOrReturn(const StatusOr<XlaOp>& op);
-
- // A helper function that runs a function that returns a StatusOr<XlaOp> and
- // returns an XlaOp.
- XlaOp ReportErrorOrReturn(const std::function<StatusOr<XlaOp>()>& op_creator);
-
- // Returns true if 'operand' is a compile-time constant. A compile-time
- // constant does not depend on any parameters, or on stateful operators such
- // as `RngNormal` or `Infeed`.
- //
- // This tests whether a computation is a compile-time constant without
- // evaluating the computation.
- StatusOr<bool> IsConstant(const XlaOp& operand) const;
-
- private:
- // Enqueues a "retrieve parameter value" instruction for a parameter that was
- // passed to the computation.
- XlaOp Parameter(int64 parameter_number, const Shape& shape,
- const string& name);
-
- // Enqueues a constant with the value of the given literal onto the
- // computation.
- XlaOp ConstantLiteral(const LiteralSlice& literal);
-
- // Enqueues a constant onto the computation. Methods are templated on the
- // native host type (NativeT) which corresponds to a specific XLA
- // PrimitiveType as given in the following table:
- //
- // Native Type PrimitiveType
- // -----------------------------
- // bool PRED
- // int32 S32
- // int64 S64
- // uint32 U32
- // uint64 U64
- // float F32
- // double F64
- //
- // Note: not all primitive types defined in xla_data.proto have a
- // corresponding native type yet.
- template <typename NativeT>
- XlaOp ConstantR0(NativeT value);
- template <typename NativeT>
- XlaOp ConstantR1(tensorflow::gtl::ArraySlice<NativeT> values);
- XlaOp ConstantR1(const tensorflow::core::Bitmap& values);
- template <typename NativeT>
- XlaOp ConstantR2(
- std::initializer_list<std::initializer_list<NativeT>> values);
- template <typename NativeT>
- XlaOp ConstantFromArrayWithLayout(const Array<NativeT>& values,
- const Layout& layout);
- template <typename NativeT>
- XlaOp ConstantFromArray(const Array<NativeT>& values);
- template <typename NativeT>
- XlaOp ConstantR2FromArray2DWithLayout(const Array2D<NativeT>& values,
- const Layout& layout);
- template <typename NativeT>
- XlaOp ConstantR2FromArray2D(const Array2D<NativeT>& values);
- template <typename NativeT>
- XlaOp ConstantR3FromArray3DWithLayout(const Array3D<NativeT>& values,
- const Layout& layout);
- template <typename NativeT>
- XlaOp ConstantR3FromArray3D(const Array3D<NativeT>& values);
- template <typename NativeT>
- XlaOp ConstantR4FromArray4DWithLayout(const Array4D<NativeT>& values,
- const Layout& layout);
- template <typename NativeT>
- XlaOp ConstantR4FromArray4D(const Array4D<NativeT>& values);
-
- // Enqueues a rank one constant (vector) onto the computation. The vector has
- // size 'length' and every element has the value 'value'.
- template <typename NativeT>
- XlaOp ConstantR1(int64 length, NativeT value);
-
- // Adds dimensions to an array by duplicating the data in the array.
- //
- // The new dimensions are inserted on the left, i.e. if
- // broadcast_sizes has values {a0, ..., aN} and the operand shape
- // has dimensions {b0, ..., bM} then the shape of the output has
- // dimensions {a0, ..., aN, b0, ..., bM}.
- //
- // The new dimensions index into copies of the operand, i.e.
- //
- // output[i0, ..., iN, j0, ..., jM] = operand[j0, ..., jM]
- XlaOp Broadcast(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> broadcast_sizes);
-
- // Performs in-dimension-style broadcast.
- //
- // Operand specifies the input to be broadcast. "shape" is expected output
- // shape. "broadcast_dimensions" are the dimensions to be broadcasting into.
- // Dimension numbers in broadcast_dimensions map to individual dimensions
- // of the operand, and specify what dimension of the output shape they
- // should be broadcast.
- // e.g.
- // Say operand = [1, 2], i.e., a 1D tensor with 2 elements.
- // and dimension of shape is [2,2].
- // Specifying {1} as brodcast_dimension will generate output
- // [1 , 2]
- // [1 , 2]
- // On the other hand, specifying {0} as broadcast_dimension
- // will generate output
- // [1 , 1]
- // [2 , 2]
- XlaOp BroadcastInDim(
- const XlaOp& operand, const Shape& shape,
- const tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
-
- // Enqueues a pad operation onto the computation that pads the given value on
- // the edges as well as between the elements of the input. padding_config
- // specifies the padding amount for each dimension.
- XlaOp Pad(const XlaOp& operand, const XlaOp& padding_value,
- const PaddingConfig& padding_config);
-
- // Enqueues an operation onto the computation that flattens the operand based
- // on the dimension order (major/slowest-varying to minor/fastest-varying)
- // given, followed by reshaping it into the shape with the given dimension
- // sizes (also major to minor). Conceptually, this is a limited form of
- // "shape casting".
- XlaOp Reshape(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> dimensions,
- tensorflow::gtl::ArraySlice<int64> new_sizes);
-
- // Enqueues an operation onto the computation that collapses the operand, from
- // first to last dimension (C order), then reshapes it to the given dimension
- // sizes. Conceptually, this is a limited form of "shape casting".
- XlaOp Reshape(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> new_sizes);
-
- // Wrapper for Reshape.
- // Enqueues an operation to collapse the provided dimensions; e.g. an
- // operand with dimensions {x=256, y=2, z=2, p=32} can be collapsed to
- // {x=1024, y=32} by collapsing dims {0, 1, 2}. Collapsing dimensions must
- // be a consecutive, in-order subsequence of the operand dimensions.
- //
- // Note that collapsing a single dimension does nothing:
- //
- // {256} collapsing {0} => {256}
- // {1} collapsing {0} => {1}
- //
- // Collapsing multiple dimensions produces a single result dimension:
- //
- // {256, 2} collapsing {0,1} => {512}
- // {256, 2, 3} collapsing {0,1} => {512, 3}
- //
- // This could potentially cause data to be moved -- it provides a more
- // structured form of reshaping than an arbitrary Reshape operation.
- XlaOp Collapse(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> dimensions);
-
- // Enqueues a slice operation onto the computation that slices the operand
- // from the start indices to the limit indices; e.g.
- //
- // x
- // [ 0 1 2 3 ]
- // y [ 4 5 6 7 ] => slice(start={1, 1}, limit={2, 3}) => [ 5 6 ]
- // [ 8 9 a b ]
- //
- // Note that "limit" means up-to-but-not-including; i.e. [start, limit) in 1D
- // range notation.
- // The strides parameter determines the stride over the slice
- XlaOp Slice(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> start_indices,
- tensorflow::gtl::ArraySlice<int64> limit_indices,
- tensorflow::gtl::ArraySlice<int64> strides);
-
- // Enqueues a slice operation in a given dimension, taking all other
- // dimensions as they are; e.g. if dimno is 1 from start_index 2 to
- // limit_index 4 by 1, and the shape is f32[7,8,9], this call is short-hand
- // for:
- //
- // array[:, 2:4:1, :]
- XlaOp SliceInDim(const XlaOp& operand, int64 start_index, int64 limit_index,
- int64 stride, int64 dimno);
-
- // Enqueues a slice operation onto the computation that slices the 'operand'
- // from dynamic start indices which are passed in 'start_indices'.
- // The size of the slice in each dimension is passed in 'slice_sizes',
- // which specify the end point of exclusive slice intervals in each
- // dimension [start, start + size).
- // The shape of 'start_indices' must be rank == 1, with dimension size
- // equal to the rank of the 'operand'.
- // Slice index calculations are computed modulo input dimension sizes to
- // prevent dynamic start indices from generating out-of-bound array accesses.
- XlaOp DynamicSlice(const XlaOp& operand, const XlaOp& start_indices,
- tensorflow::gtl::ArraySlice<int64> slice_sizes);
-
- // Enqueues a dynamic update slice operation onto the computation, which
- // updates a slice of 'operand' with 'update' at dynamic 'start_indices'.
- // The shape of 'update' determines the shape of the slice of 'operand'
- // which is updated.
- // The indices specified in 'start_indices' specify the offset of the slice
- // of 'operand' which is updated.
- //
- // update = {10, 11} // calculated at runtime.
- // [1 2 3] start = {1, 1} // calculated at runtime. [1 2 3 ]
- // [4 5 6] => DynamicUpdateslice(data, update, start) => [4 10 11]
- // [7 8 9] [7 8 9 ]
- //
- // The shape of 'start_indices' must be rank == 1, with dimension size
- // equal to the rank of the 'operand'.
- // Slice index calculations are computed modulo update dimension sizes to
- // prevent dynamic start indices from generating out-of-bound array accesses.
- XlaOp DynamicUpdateSlice(const XlaOp& operand, const XlaOp& update,
- const XlaOp& start_indices);
-
- // Enqueues a concatenate instruction onto the computation. 'operands' must
- // have >= 1 entry.
- XlaOp ConcatInDim(tensorflow::gtl::ArraySlice<XlaOp> operands,
- int64 dimension);
-
- // Enqueue a tracing operation onto the computation; the computation will emit
- // a logging message with the operand.
- void Trace(const string& tag, const XlaOp& operand);
-
- // Enqueues a conditional-move-like select operation onto the computation;
- // predicated on pred, selects between on_true and on_false.
- XlaOp Select(const XlaOp& pred, const XlaOp& on_true, const XlaOp& on_false);
-
- // Enqueues a tuple-creation instruction onto the computation.
- XlaOp Tuple(tensorflow::gtl::ArraySlice<XlaOp> elements);
-
- // Enqueues a tuple-element-get instruction onto the computation.
- XlaOp GetTupleElement(const XlaOp& tuple_data, int64 index);
-
- // Enqueues an equal-to comparison instruction onto the computation.
- XlaOp Eq(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- // Enqueues a not-equal comparison instruction onto the computation.
- XlaOp Ne(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- // Enqueues a greater-or-equal comparison instruction onto the computation.
- XlaOp Ge(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- // Enqueues a greater-than comparison instruction onto the computation.
- XlaOp Gt(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- // Enqueues a less-than comparison instruction onto the computation.
- XlaOp Lt(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- // Enqueues a less-or-equal comparison instruction onto the computation.
- XlaOp Le(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- // Enqueues a dot instruction onto the computation.
- XlaOp Dot(const XlaOp& lhs, const XlaOp& rhs);
-
- // Enqueues a general dot instruction onto the computation.
- XlaOp DotGeneral(const XlaOp& lhs, const XlaOp& rhs,
- const DotDimensionNumbers& dimension_numbers);
-
- // Enqueues a convolution instruction onto the computation, which uses the
- // default convolution dimension numbers.
- XlaOp Conv(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- Padding padding);
-
- // Enqueues a convolution instruction onto the computation, with the caller
- // provided padding configuration in the format returned by MakePadding().
- XlaOp ConvWithGeneralPadding(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding);
-
- // Enqueues a convolution instruction onto the computation, with the caller
- // provided dimension numbers configuration.
- XlaOp ConvWithGeneralDimensions(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> window_strides, Padding padding,
- const ConvolutionDimensionNumbers& dimension_numbers);
-
- // Enqueues a convolution instruction onto the computation, with the caller
- // provided padding configuration as well as the dimension numbers.
- XlaOp ConvGeneral(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
- const ConvolutionDimensionNumbers& dimension_numbers);
-
- // Enqueues a convolution instruction onto the computation, with the caller
- // provided padding configuration, dilation factors and dimension numbers.
- XlaOp ConvGeneralDilated(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
- tensorflow::gtl::ArraySlice<int64> lhs_dilation,
- tensorflow::gtl::ArraySlice<int64> rhs_dilation,
- const ConvolutionDimensionNumbers& dimension_numbers);
-
- // Enqueues an FFT instruction onto the computation, of the given type and
- // with the given FFT length.
- XlaOp Fft(const XlaOp& operand, FftType fft_type,
- tensorflow::gtl::ArraySlice<int64> fft_length);
-
- // Enqueues an infeed instruction onto the computation, which writes data of
- // the given shape to the infeed buffer of the device.
- XlaOp Infeed(const Shape& shape, const string& config = "");
- XlaOp InfeedWithToken(const XlaOp& token, const Shape& shape,
- const string& config = "");
-
- // Enqueues an outfeed instruction onto the computation. This instruction
- // generates outgoing data transfers for the given data.
- //
- // shape_with_layout communicates the laid out shape that we want to outfeed
- // -- if !ShapeUtil::Compatible(GetShape(operand), shape_with_layout) an error
- // will occur.
- void Outfeed(const XlaOp& operand, const Shape& shape_with_layout,
- const string& outfeed_config);
- XlaOp OutfeedWithToken(const XlaOp& operand, const XlaOp& token,
- const Shape& shape_with_layout,
- const string& outfeed_config);
-
- // Enqueues a call instruction onto the computation.
- XlaOp Call(const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<XlaOp> operands);
-
- // Enqueues a custom call instruction onto the computation.
- // During code generation, a call instruction is emitted which targets a
- // symbol with the name |call_target_name|. The |operands| are passed to the
- // call instruction. |shape| is the resultant shape.
- XlaOp CustomCall(const string& call_target_name,
- tensorflow::gtl::ArraySlice<XlaOp> operands,
- const Shape& shape);
-
- // Enqueues a pseudo-op to represent host-side computation data-dependencies.
- // During code generation, host send and receive operations will be generated
- // to transfer |operands| to the host and a single result of |shape| back to
- // the device. Host send/recv operations are emitted using |channel_name|.
- // Dataflow dependencies and the |cost_estimate_ns| field may be used in HLO
- // instruction scheduling.
- XlaOp HostCompute(tensorflow::gtl::ArraySlice<XlaOp> operands,
- const string& channel_name, int64 cost_estimate_ns,
- const Shape& shape);
-
- // The following methods enqueue element-wise binary arithmetic operations
- // onto the computation. The shapes of the operands have to match unless one
- // of the operands is a scalar, or an explicit broadcast dimension is given
- // (see g3doc for more details).
-
- // Enqueues a complex compose instruction onto the computation.
- XlaOp Complex(const XlaOp& real, const XlaOp& imag,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- // Enqueues a complex conjugate instruction onto the computation.
- XlaOp Conj(const XlaOp& operand);
-
- // Enqueues an add instruction onto the computation.
- XlaOp Add(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- // Enqueues a subtract instruction onto the computation.
- XlaOp Sub(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- // Enqueues a multiply instruction onto the computation.
- XlaOp Mul(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- // Enqueues a divide instruction onto the computation.
- XlaOp Div(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- // Enqueues a remainder instruction onto the computation.
- XlaOp Rem(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- // Enqueues a max instruction onto the computation.
- XlaOp Max(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- // Enqueues a min instruction onto the computation.
- XlaOp Min(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- // Element-wise logical operators
- XlaOp And(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- XlaOp Or(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- XlaOp Xor(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- XlaOp Not(const XlaOp& operand);
-
- XlaOp ShiftLeft(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
- XlaOp ShiftRightArithmetic(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
- XlaOp ShiftRightLogical(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- // Reduces an array among the provided dimensions, given "computation" as a
- // reduction operator.
- XlaOp Reduce(const XlaOp& operand, const XlaOp& init_value,
- const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<int64> dimensions_to_reduce);
-
- // Convenience wrapper around the above that reduces all the dimensions in the
- // operand shape.
- XlaOp ReduceAll(const XlaOp& operand, const XlaOp& init_value,
- const XlaComputation& computation);
-
- // Enqueues a windowed reduce instruction onto the computation.
- XlaOp ReduceWindow(const XlaOp& operand, const XlaOp& init_value,
- const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<int64> window_dimensions,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- Padding padding);
-
- // As ReduceWindow(), but the padding is given in the format
- // returned by MakePadding().
- XlaOp ReduceWindowWithGeneralPadding(
- const XlaOp& operand, const XlaOp& init_value,
- const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<int64> window_dimensions,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding);
-
- // Returns the sum of the operand value within each subgroup of replicas. All
- // replicas supply one input to the sum and all replicas receive the resulting
- // sum for each subgroup.
- XlaOp CrossReplicaSum(
- const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> replica_group_ids = {});
-
- // Enqueues an operation that do an AllReduce of the operand cross cores. Here
- // AllReduce means doing a reduction on the input operand cross cores and then
- // broadcasting the reduction result to those cores. The reduction function is
- // defined by `computation`, which should be a commutative computation on
- // scalars, e.g., add, min, or max. The way that AllReduce is applied is
- // configured by:
- //
- // - `replica_group_ids`: maps replica ids to subgroup ids. If empty, all
- // replicas belong to one group. Allreduce will be applied within subgroups.
- // For example, we have 4 replicas, then replica_group_ids={0,1,0,1} means,
- // replica 0 and 2 are in subgroup 0, replica 1 and 3 are in subgroup 1.
- //
- // - `channel_id`: for Allreduce nodes from different models, if they have the
- // same channel_id, they will be 'Allreduce'd. If empty, Allreduce will not be
- // applied cross models.
- //
- // TODO(b/79737069): Rename this to AllReduce when it's ready to use.
- XlaOp CrossReplicaSum(
- const XlaOp& operand, const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<int64> replica_group_ids = {},
- const tensorflow::gtl::optional<ChannelHandle>& channel_id =
- tensorflow::gtl::nullopt);
-
- // Enqueues an operation that scatters the `source` array to the selected
- // indices of each window.
- XlaOp SelectAndScatter(const XlaOp& operand, const XlaComputation& select,
- tensorflow::gtl::ArraySlice<int64> window_dimensions,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- Padding padding, const XlaOp& source,
- const XlaOp& init_value,
- const XlaComputation& scatter);
-
- // As SelectAndScatter(), but the padding is given in the format
- // returned by MakePadding().
- XlaOp SelectAndScatterWithGeneralPadding(
- const XlaOp& operand, const XlaComputation& select,
- tensorflow::gtl::ArraySlice<int64> window_dimensions,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
- const XlaOp& source, const XlaOp& init_value,
- const XlaComputation& scatter);
-
- // Enqueues an abs instruction onto the computation.
- XlaOp Abs(const XlaOp& operand);
-
- // Enqueues a atan2 instruction onto the computation.
- XlaOp Atan2(const XlaOp& y, const XlaOp& x,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- // Enqueues an exp instruction onto the computation.
- XlaOp Exp(const XlaOp& operand);
-
- // Enqueues an expm1 instruction onto the computation.
- XlaOp Expm1(const XlaOp& operand);
-
- // Enqueues a floor instruction onto the computation.
- XlaOp Floor(const XlaOp& operand);
-
- // Enqueues a ceil instruction onto the computation.
- XlaOp Ceil(const XlaOp& operand);
-
- // Enqueues a round instruction onto the computation, rounding to nearest even
- // with half-way cases rounding away from zero.
- XlaOp Round(const XlaOp& operand);
-
- // Enqueues an log instruction (natural logarithm) onto the computation.
- XlaOp Log(const XlaOp& operand);
-
- // Enqueues an log1p instruction (log(x+1)) onto the computation.
- XlaOp Log1p(const XlaOp& operand);
-
- // Enqueues a sign instruction onto the computation.
- XlaOp Sign(const XlaOp& operand);
-
- // Enqueues a count leading zeros instruction onto the computation.
- XlaOp Clz(const XlaOp& operand);
-
- // Enqueues a cosine instruction onto the computation.
- XlaOp Cos(const XlaOp& operand);
-
- // Enqueues a sine instruction onto the computation.
- XlaOp Sin(const XlaOp& operand);
-
- // Enqueues a tanh instruction onto the computation.
- XlaOp Tanh(const XlaOp& operand);
-
- // Enqueues a real-part instruction onto the computation.
- XlaOp Real(const XlaOp& operand);
-
- // Enqueues an imaginary-part instruction onto the computation.
- XlaOp Imag(const XlaOp& operand);
-
- // Enqueues a lhs^rhs computation onto the computation.
- XlaOp Pow(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
- // Enqueues an operator that tests if the operand's values are finite, i.e.,
- // not Inf or NaN. Defined only for floating-point types. Returns an array of
- // booleans with the same shape where entries are true iff the corresponding
- // entry was NaN.
- XlaOp IsFinite(const XlaOp& operand);
-
- // Enqueues a convert instruction onto the computation that changes the
- // element type of the operand array to primitive_type.
- XlaOp ConvertElementType(const XlaOp& operand,
- PrimitiveType new_element_type);
-
- // Enqueues a no-op instruction onto the computation that changes
- // the element type of the operand array to primitive_type. The
- // bit-widths of the source and destination element types must be
- // identical.
- XlaOp BitcastConvertType(const XlaOp& operand,
- PrimitiveType new_element_type);
-
- // Enqueues a negate instruction onto the computation.
- XlaOp Neg(const XlaOp& operand);
-
- // Enqueues a transpose instruction onto the computation.
- XlaOp Transpose(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> permutation);
-
- // Enqueues a reverse instruction onto the computation. The order of the
- // elements in the given dimensions is reversed (i.e., the element at index i
- // is moved to index dimension_size - 1 - i).
- XlaOp Rev(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> dimensions);
-
- // Enqueues a sort (as increasing order) instruction onto the computation.
- // If only keys are provided:
- // * If the keys are an rank-1 tensor (an array), the result is a sorted array
- // of keys, in ascending order.
- // * If the keys have higher rank, the keys are sorted along the provided
- // dimension. For example, for a rank-2 tensor (a matrix) of keys, a dimension
- // value of 0 will indepenently sort every column, and a dimension value of 1
- // will independently sort each row. If no dimension number is provided, then
- // the last dimension is chosen by default.
- //
- // If both keys and values are provided:
- // * The keys and the values must tensors with the same dimensions. The
- // element types of the tensors may be different.
- // * The result is a tuple that consists of a sorted tensor of keys (along the
- // provided dimension, as above) as the first element, and a tensor with their
- // corresponding values as the second element.
- XlaOp Sort(XlaOp keys,
- tensorflow::gtl::optional<XlaOp> values = tensorflow::gtl::nullopt,
- int64 dimension = -1);
-
- // Enqueues a clamp instruction onto the computation.
- XlaOp Clamp(const XlaOp& min, const XlaOp& operand, const XlaOp& max);
-
- // Enqueues a map instruction onto the computation.
- XlaOp Map(tensorflow::gtl::ArraySlice<XlaOp> operands,
- const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<int64> dimensions,
- tensorflow::gtl::ArraySlice<XlaOp> static_operands = {});
-
- // Enqueues a N(mu, sigma) random number generation instruction onto the
- // computation.
- XlaOp RngNormal(const XlaOp& mu, const XlaOp& sigma, const Shape& shape);
-
- // Enqueues a U(a, b) random number generation instruction onto the
- // computation. Returns values in the semi-open interval [a, b).
- XlaOp RngUniform(const XlaOp& a, const XlaOp& b, const Shape& shape);
-
- // Enqueues a while node onto the computation.
- XlaOp While(const XlaComputation& condition, const XlaComputation& body,
- const XlaOp& init);
-
- // Enqueues a conditional node onto the computation.
- XlaOp Conditional(const XlaOp& predicate, const XlaOp& true_operand,
- const XlaComputation& true_computation,
- const XlaOp& false_operand,
- const XlaComputation& false_computation);
-
- // Enqueues a ReducePrecision node onto the computation.
- XlaOp ReducePrecision(const XlaOp& operand, const int exponent_bits,
- const int mantissa_bits);
-
- // Enqueues a Gather node onto the computation.
- XlaOp Gather(const XlaOp& input, const XlaOp& gather_indices,
- const GatherDimensionNumbers& dimension_numbers,
- tensorflow::gtl::ArraySlice<int64> window_bounds);
-
- // Enqueues a Send node onto the computation for device-to-device
- // communication, to send the given operand to a Recv instruction that shares
- // the same channel handle.
- void Send(const XlaOp& operand, const ChannelHandle& handle);
- XlaOp SendWithToken(const XlaOp& operand, const XlaOp& token,
- const ChannelHandle& handle);
-
- // Enqueues a Send node which sends data to the host.
- XlaOp SendToHost(const XlaOp& operand, const XlaOp& token,
- const Shape& shape_with_layout, const ChannelHandle& handle);
-
- // Enqueues a Recv node which receives data from the host.
- XlaOp RecvFromHost(const XlaOp& token, const Shape& shape,
- const ChannelHandle& handle);
-
- // Enqueues an AfterAll operation with no operands producing a token-shaped
- // value.
- XlaOp CreateToken();
-
- // Enqueues an AfterAll operation with no operands producing a token-shaped
- // value.
- XlaOp AfterAll(tensorflow::gtl::ArraySlice<XlaOp> tokens);
-
- // Enqueues a Recv node onto the computation. The data comes from a Send
- // instruction that shares the same channel handle and its shape must
- // be the same as the given shape.
- XlaOp Recv(const Shape& shape, const ChannelHandle& handle);
- XlaOp RecvWithToken(const XlaOp& token, const Shape& shape,
- const ChannelHandle& handle);
-
- // Normalizes operand across spatial and batch dimensions for each feature.
- //
- // Returns a tuple (normalized, batch_mean, batch_var) where `normalized`
- // is the normalized result and batch_mean and batch_var are the mean and
- // variance, respectively, across batch for the operand.
- XlaOp BatchNormTraining(const XlaOp& operand, const XlaOp& scale,
- const XlaOp& offset, float epsilon,
- int64 feature_index);
-
- // Normalizes operand across spatial and batch dimensions for each feature.
- //
- // `BatchNormInference` is equivalent to calling `BatchNormTraining` without
- // computing `mean` and `variance` for each batch inside the operation. It
- // uses the input `mean` and `variance` instead as estimated values. The
- // purpose of this op is to reduce latency in inference, hence the name
- // `BatchNormInference`.
- //
- // The output has the same shape as `operand`, and contains the normalized
- // values for each batch.
- XlaOp BatchNormInference(const XlaOp& operand, const XlaOp& scale,
- const XlaOp& offset, const XlaOp& mean,
- const XlaOp& variance, float epsilon,
- int64 feature_index);
-
- // Calculates the gradients of a batch norm op.
- //
- // The inputs `batch_mean` and `batch_var` represent the mean and variance
- // across the batch.
- //
- // Returns a tuple of three elements:
- // - grad_operand: Gradient with respect to input `operand`
- // - grad_offset: Gradient with respect to input `offset`
- // - grad_scale: Gradient with respect to input `scale`
- XlaOp BatchNormGrad(const XlaOp& operand, const XlaOp& scale,
- const XlaOp& batch_mean, const XlaOp& batch_var,
- const XlaOp& grad_output, float epsilon,
- int64 feature_index);
-
- StatusOr<XlaOp> AddInstruction(
- HloInstructionProto&& instr, HloOpcode opcode,
- tensorflow::gtl::ArraySlice<XlaOp> operands = {});
-
- void AddCalledComputation(const XlaComputation& computation,
- HloInstructionProto* instr);
-
- StatusOr<const HloInstructionProto*> LookUpInstruction(const XlaOp& op) const;
-
- // Internal helper method that does the building for an arbitrary unary op.
- XlaOp UnaryOp(HloOpcode unop, const XlaOp& operand);
-
- // Internal helper method that does the building for an arbitrary binary op.
- // broadcast_dimensions specifies which dimensions to use for broadcasting
- // when the operation is between tensors of different ranks.
- XlaOp BinaryOp(HloOpcode binop, const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
-
- // Internal helper method that does the building for an arbitrary ternary op.
- XlaOp TernaryOp(HloOpcode triop, const XlaOp& lhs, const XlaOp& rhs,
- const XlaOp& ehs);
-
- XlaOp RngOp(RandomDistribution distribution,
- tensorflow::gtl::ArraySlice<XlaOp> parameters,
- const Shape& shape);
-
- StatusOr<XlaOp> InDimBroadcast(
- const Shape& shape, const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
-
- // Internal helper method that creates a sequence of instructions that
- // performs an explicit broadcast of the operand to the target shape.
- StatusOr<XlaOp> AddBroadcastSequence(const Shape& output_shape,
- const XlaOp& operand);
-
- // Internal helper method for creating a Reshape op with the already inferred
- // shape.
- StatusOr<XlaOp> Reshape(const Shape& shape, const XlaOp& operand);
-
- // Returns the (inferred) result for the program shape for the current
- // computation and fills the root_id in the pointer.
- StatusOr<ProgramShape> GetProgramShape(int64* root_id) const;
-
- // Returns shapes for the operands.
- StatusOr<std::vector<Shape>> GetOperandShapes(
- tensorflow::gtl::ArraySlice<XlaOp> operands) const;
-
- // A visitor which checks whether an operation is a compile-time constant,
- // meaning that it doesn't depend on any parameters, or on any stateful
- // operation such as `RngNormal` or `Infeed`. The visitor walks the
- // computation starting at a given operation and sets is_constant to false iff
- // a parameter or stateful operation is encountered.
- void IsConstantVisitor(const int64 op_handle, std::set<int64>* visited,
- bool* is_constant) const;
-
- // Checks bounds for convolution parameters.
- Status VerifyConvolution(
- const Shape& lhs_shape, const Shape& rhs_shape,
- const ConvolutionDimensionNumbers& dimension_numbers) const;
-
- // Helper function for creating a Window proto from user-supplied data.
- // Returns error if the user-supplied data was invalid.
- StatusOr<Window> MakeWindow(
- tensorflow::gtl::ArraySlice<int64> window_dimensions,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
- tensorflow::gtl::ArraySlice<int64> lhs_dilation,
- tensorflow::gtl::ArraySlice<int64> rhs_dilation) const;
-
- string name_; // Name to use for the built computation.
-
- // The first error encountered while building the computation.
- // This is OK until the first error is encountered.
- Status first_error_;
-
- // The saved stack trace from the point at which the first error occurred.
- tensorflow::SavedStackTrace first_error_backtrace_;
-
- // The instructions of this computation.
- std::vector<HloInstructionProto> instructions_;
-
- // The embedded computations used by this computation. Each computation was
- // the entry computation of some XlaComputation, the key is the unique id of
- // that XlaComputation.
- std::map<int64, HloComputationProto> embedded_;
-
- // The unique parameter numbers.
- tensorflow::gtl::FlatSet<int64> parameter_numbers_;
-
- // The metadata to attach to each op. This is structured as a "modal"-like
- // operation, in order to simplify client code (and not sprinkle this metadata
- // throughout the TensorFlow op kernel implementations).
- OpMetadata metadata_;
-
- // Sharding for this operator. This is structured as a "model"-like operation,
- // in order to simplify client code, similar to metadata_.
- tensorflow::gtl::optional<OpSharding> sharding_;
-
- // Mode bit that indicates whether to die when a first error is encountered.
- bool die_immediately_on_error_ = false;
-
- XlaBuilder* parent_builder_{nullptr};
-
- friend XlaOp Parameter(XlaBuilder* builder, int64 parameter_number,
- const Shape& shape, const string& name);
- friend XlaOp ConstantLiteral(XlaBuilder* builder,
- const LiteralSlice& literal);
- template <typename NativeT>
- friend XlaOp ConstantR0(XlaBuilder* builder, NativeT value);
- template <typename NativeT>
- friend XlaOp ConstantR1(XlaBuilder* builder,
- tensorflow::gtl::ArraySlice<NativeT> values);
- friend XlaOp ConstantR1(XlaBuilder* builder,
- const tensorflow::core::Bitmap& values);
- template <typename NativeT>
- friend XlaOp ConstantR2(
- XlaBuilder* builder,
- std::initializer_list<std::initializer_list<NativeT>> values);
- template <typename NativeT>
- friend XlaOp ConstantFromArrayWithLayout(XlaBuilder* builder,
- const Array<NativeT>& values,
- const Layout& layout);
- template <typename NativeT>
- friend XlaOp ConstantFromArray(XlaBuilder* builder,
- const Array<NativeT>& values);
- template <typename NativeT>
- friend XlaOp ConstantR2FromArray2DWithLayout(XlaBuilder* builder,
- const Array2D<NativeT>& values,
- const Layout& layout);
- template <typename NativeT>
- friend XlaOp ConstantR2FromArray2D(XlaBuilder* builder,
- const Array2D<NativeT>& values);
- template <typename NativeT>
- friend XlaOp ConstantR3FromArray3DWithLayout(XlaBuilder* builder,
- const Array3D<NativeT>& values,
- const Layout& layout);
- template <typename NativeT>
- friend XlaOp ConstantR3FromArray3D(XlaBuilder* builder,
- const Array3D<NativeT>& values);
- template <typename NativeT>
- friend XlaOp ConstantR4FromArray4DWithLayout(XlaBuilder* builder,
- const Array4D<NativeT>& values,
- const Layout& layout);
- template <typename NativeT>
- friend XlaOp ConstantR4FromArray4D(XlaBuilder* builder,
- const Array4D<NativeT>& values);
-
- template <typename NativeT>
- friend XlaOp ConstantR1(XlaBuilder* builder, int64 length, NativeT value);
-
- friend XlaOp Broadcast(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> broadcast_sizes);
-
- friend XlaOp BroadcastInDim(
- const XlaOp& operand, const Shape& shape,
- const tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
-
- friend XlaOp Pad(const XlaOp& operand, const XlaOp& padding_value,
- const PaddingConfig& padding_config);
-
- friend XlaOp Reshape(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> dimensions,
- tensorflow::gtl::ArraySlice<int64> new_sizes);
-
- friend XlaOp Reshape(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> new_sizes);
-
- friend XlaOp Collapse(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> dimensions);
-
- friend XlaOp Slice(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> start_indices,
- tensorflow::gtl::ArraySlice<int64> limit_indices,
- tensorflow::gtl::ArraySlice<int64> strides);
-
- friend XlaOp SliceInDim(const XlaOp& operand, int64 start_index,
- int64 limit_index, int64 stride, int64 dimno);
-
- friend XlaOp DynamicSlice(const XlaOp& operand, const XlaOp& start_indices,
- tensorflow::gtl::ArraySlice<int64> slice_sizes);
-
- friend XlaOp DynamicUpdateSlice(const XlaOp& operand, const XlaOp& update,
- const XlaOp& start_indices);
-
- friend XlaOp ConcatInDim(XlaBuilder* builder,
- tensorflow::gtl::ArraySlice<XlaOp> operands,
- int64 dimension);
-
- friend void Trace(const string& tag, const XlaOp& operand);
-
- friend XlaOp Select(const XlaOp& pred, const XlaOp& on_true,
- const XlaOp& on_false);
- friend XlaOp Tuple(XlaBuilder* builder,
- tensorflow::gtl::ArraySlice<XlaOp> elements);
- friend XlaOp GetTupleElement(const XlaOp& tuple_data, int64 index);
- friend XlaOp Eq(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Ne(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Ge(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Gt(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Lt(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Le(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Dot(const XlaOp& lhs, const XlaOp& rhs);
- friend XlaOp DotGeneral(const XlaOp& lhs, const XlaOp& rhs,
- const DotDimensionNumbers& dimension_numbers);
- friend XlaOp Conv(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- Padding padding);
- friend XlaOp ConvWithGeneralPadding(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding);
- friend XlaOp ConvWithGeneralDimensions(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> window_strides, Padding padding,
- const ConvolutionDimensionNumbers& dimension_numbers);
- friend XlaOp ConvGeneral(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
- const ConvolutionDimensionNumbers& dimension_numbers);
- friend XlaOp ConvGeneralDilated(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
- tensorflow::gtl::ArraySlice<int64> lhs_dilation,
- tensorflow::gtl::ArraySlice<int64> rhs_dilation,
- const ConvolutionDimensionNumbers& dimension_numbers);
- friend XlaOp Fft(const XlaOp& operand, FftType fft_type,
- tensorflow::gtl::ArraySlice<int64> fft_length);
- friend XlaOp Infeed(XlaBuilder* builder, const Shape& shape,
- const string& config);
- friend void Outfeed(const XlaOp& operand, const Shape& shape_with_layout,
- const string& outfeed_config);
- friend XlaOp Call(XlaBuilder* builder, const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<XlaOp> operands);
- friend XlaOp CustomCall(XlaBuilder* builder, const string& call_target_name,
- tensorflow::gtl::ArraySlice<XlaOp> operands,
- const Shape& shape);
- friend XlaOp HostCompute(XlaBuilder* builder,
- tensorflow::gtl::ArraySlice<XlaOp> operands,
- const string& channel_name, int64 cost_estimate_ns,
- const Shape& shape);
- friend XlaOp Complex(const XlaOp& real, const XlaOp& imag,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Conj(const XlaOp& operand);
- friend XlaOp Add(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Sub(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Mul(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Div(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Rem(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Max(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Min(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp And(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Or(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Xor(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Not(const XlaOp& operand);
- friend XlaOp ShiftLeft(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp ShiftRightArithmetic(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp ShiftRightLogical(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Reduce(const XlaOp& operand, const XlaOp& init_value,
- const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<int64> dimensions_to_reduce);
- friend XlaOp ReduceAll(const XlaOp& operand, const XlaOp& init_value,
- const XlaComputation& computation);
- friend XlaOp ReduceWindow(
- const XlaOp& operand, const XlaOp& init_value,
- const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<int64> window_dimensions,
- tensorflow::gtl::ArraySlice<int64> window_strides, Padding padding);
- friend XlaOp ReduceWindowWithGeneralPadding(
- const XlaOp& operand, const XlaOp& init_value,
- const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<int64> window_dimensions,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding);
- friend XlaOp CrossReplicaSum(
- const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> replica_group_ids);
- friend XlaOp CrossReplicaSum(
- const XlaOp& operand, const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<int64> replica_group_ids,
- const tensorflow::gtl::optional<ChannelHandle>& channel_id);
- friend XlaOp SelectAndScatter(
- const XlaOp& operand, const XlaComputation& select,
- tensorflow::gtl::ArraySlice<int64> window_dimensions,
- tensorflow::gtl::ArraySlice<int64> window_strides, Padding padding,
- const XlaOp& source, const XlaOp& init_value,
- const XlaComputation& scatter);
- friend XlaOp SelectAndScatterWithGeneralPadding(
- const XlaOp& operand, const XlaComputation& select,
- tensorflow::gtl::ArraySlice<int64> window_dimensions,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
- const XlaOp& source, const XlaOp& init_value,
- const XlaComputation& scatter);
- friend XlaOp Abs(const XlaOp& operand);
- friend XlaOp Atan2(const XlaOp& y, const XlaOp& x,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp Exp(const XlaOp& operand);
- friend XlaOp Expm1(const XlaOp& operand);
- friend XlaOp Floor(const XlaOp& operand);
- friend XlaOp Ceil(const XlaOp& operand);
- friend XlaOp Round(const XlaOp& operand);
- friend XlaOp Log(const XlaOp& operand);
- friend XlaOp Log1p(const XlaOp& operand);
- friend XlaOp Sign(const XlaOp& operand);
- friend XlaOp Clz(const XlaOp& operand);
- friend XlaOp Cos(const XlaOp& operand);
- friend XlaOp Sin(const XlaOp& operand);
- friend XlaOp Tanh(const XlaOp& operand);
- friend XlaOp Real(const XlaOp& operand);
- friend XlaOp Imag(const XlaOp& operand);
- friend XlaOp Pow(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
- friend XlaOp IsFinite(const XlaOp& operand);
- // TODO(b/64798317): Finish CPU & GPU implementation, then replace xla::Iota
- // in xla/client/lib/numeric.h with this (renamed to xla::Iota).
- friend XlaOp IotaGen(XlaBuilder* builder, PrimitiveType type, int64 size);
- friend XlaOp ConvertElementType(const XlaOp& operand,
- PrimitiveType new_element_type);
- friend XlaOp BitcastConvertType(const XlaOp& operand,
- PrimitiveType new_element_type);
- friend XlaOp Neg(const XlaOp& operand);
- friend XlaOp Transpose(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> permutation);
- friend XlaOp Rev(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> dimensions);
- friend XlaOp Sort(XlaOp keys, tensorflow::gtl::optional<XlaOp> values,
- int64 dimension);
- friend XlaOp Clamp(const XlaOp& min, const XlaOp& operand, const XlaOp& max);
- friend XlaOp Map(XlaBuilder* builder,
- tensorflow::gtl::ArraySlice<XlaOp> operands,
- const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<int64> dimensions,
- tensorflow::gtl::ArraySlice<XlaOp> static_operands);
- friend XlaOp RngNormal(const XlaOp& mu, const XlaOp& sigma,
- const Shape& shape);
- friend XlaOp RngUniform(const XlaOp& a, const XlaOp& b, const Shape& shape);
- friend XlaOp While(const XlaComputation& condition,
- const XlaComputation& body, const XlaOp& init);
- friend XlaOp Conditional(const XlaOp& predicate, const XlaOp& true_operand,
- const XlaComputation& true_computation,
- const XlaOp& false_operand,
- const XlaComputation& false_computation);
- friend XlaOp ReducePrecision(const XlaOp& operand, const int exponent_bits,
- const int mantissa_bits);
- friend XlaOp Gather(const XlaOp& input, const XlaOp& gather_indices,
- const GatherDimensionNumbers& dimension_numbers,
- tensorflow::gtl::ArraySlice<int64> window_bounds);
- friend void Send(const XlaOp& operand, const ChannelHandle& handle);
- friend XlaOp Recv(XlaBuilder* builder, const Shape& shape,
- const ChannelHandle& handle);
- friend XlaOp BatchNormTraining(const XlaOp& operand, const XlaOp& scale,
- const XlaOp& offset, float epsilon,
- int64 feature_index);
- friend XlaOp BatchNormInference(const XlaOp& operand, const XlaOp& scale,
- const XlaOp& offset, const XlaOp& mean,
- const XlaOp& variance, float epsilon,
- int64 feature_index);
- friend XlaOp BatchNormGrad(const XlaOp& operand, const XlaOp& scale,
- const XlaOp& batch_mean, const XlaOp& batch_var,
- const XlaOp& grad_output, float epsilon,
- int64 feature_index);
- friend XlaOp SendWithToken(const XlaOp& operand, const XlaOp& token,
- const ChannelHandle& handle);
- friend XlaOp RecvWithToken(const XlaOp& token, const Shape& shape,
- const ChannelHandle& handle);
- friend XlaOp SendToHost(const XlaOp& operand, const XlaOp& token,
- const Shape& shape_with_layout,
- const ChannelHandle& handle);
- friend XlaOp RecvFromHost(const XlaOp& token, const Shape& shape,
- const ChannelHandle& handle);
- friend XlaOp InfeedWithToken(const XlaOp& token, const Shape& shape,
- const string& config);
- friend XlaOp OutfeedWithToken(const XlaOp& operand, const XlaOp& token,
- const Shape& shape_with_layout,
- const string& outfeed_config);
- friend XlaOp CreateToken(XlaBuilder* builder);
- friend XlaOp AfterAll(XlaBuilder* builder,
- tensorflow::gtl::ArraySlice<XlaOp> tokens);
-};
-
-// RAII-style object: sets the current sharding assignment in builder on
-// construction, and sets back to the previous assignment on destruction.
-class XlaScopedShardingAssignment {
- public:
- XlaScopedShardingAssignment(xla::XlaBuilder* builder,
- tensorflow::gtl::optional<OpSharding> sharding)
- : builder_(builder), prev_sharding_(builder->sharding()) {
- SetSharding(sharding);
- }
-
- XlaScopedShardingAssignment(const XlaScopedShardingAssignment&) = delete;
- XlaScopedShardingAssignment& operator=(const XlaScopedShardingAssignment&) =
- delete;
-
- ~XlaScopedShardingAssignment() { SetSharding(prev_sharding_); }
-
- private:
- void SetSharding(const tensorflow::gtl::optional<OpSharding>& sharding) {
- if (sharding.has_value()) {
- builder_->SetSharding(sharding.value());
- } else {
- builder_->ClearSharding();
- }
- }
-
- xla::XlaBuilder* const builder_;
- tensorflow::gtl::optional<OpSharding> prev_sharding_;
-};
-
-// Free functions for building XlaOps. The intention is that these will
-// become the public API for building XlaOps rather than calling methods on
-// XlaBuilder directly.
-
-// Enqueues a "retrieve parameter value" instruction for a parameter that was
-// passed to the computation.
-XlaOp Parameter(XlaBuilder* builder, int64 parameter_number, const Shape& shape,
- const string& name);
-
-// Enqueues a constant with the value of the given literal onto the
-// computation.
-XlaOp ConstantLiteral(XlaBuilder* builder, const LiteralSlice& literal);
-
-// Enqueues a constant onto the computation. Methods are templated on the
-// native host type (NativeT) which corresponds to a specific XLA
-// PrimitiveType as given in the following table:
-//
-// Native Type PrimitiveType
-// -----------------------------
-// bool PRED
-// int32 S32
-// int64 S64
-// uint32 U32
-// uint64 U64
-// float F32
-// double F64
-//
-// Note: not all primitive types defined in xla_data.proto have a
-// corresponding native type yet.
-template <typename NativeT>
-XlaOp ConstantR0(XlaBuilder* builder, NativeT value);
-template <typename NativeT>
-XlaOp ConstantR1(XlaBuilder* builder,
- tensorflow::gtl::ArraySlice<NativeT> values);
-XlaOp ConstantR1(XlaBuilder* builder, const tensorflow::core::Bitmap& values);
-template <typename NativeT>
-XlaOp ConstantR2(XlaBuilder* builder,
- std::initializer_list<std::initializer_list<NativeT>> values);
-template <typename NativeT>
-XlaOp ConstantFromArrayWithLayout(XlaBuilder* builder,
- const Array<NativeT>& values,
- const Layout& layout);
-template <typename NativeT>
-XlaOp ConstantFromArray(XlaBuilder* builder, const Array<NativeT>& values);
-template <typename NativeT>
-XlaOp ConstantR2FromArray2DWithLayout(XlaBuilder* builder,
- const Array2D<NativeT>& values,
- const Layout& layout);
-template <typename NativeT>
-XlaOp ConstantR2FromArray2D(XlaBuilder* builder,
- const Array2D<NativeT>& values);
-template <typename NativeT>
-XlaOp ConstantR3FromArray3DWithLayout(XlaBuilder* builder,
- const Array3D<NativeT>& values,
- const Layout& layout);
-template <typename NativeT>
-XlaOp ConstantR3FromArray3D(XlaBuilder* builder,
- const Array3D<NativeT>& values);
-template <typename NativeT>
-XlaOp ConstantR4FromArray4DWithLayout(XlaBuilder* builder,
- const Array4D<NativeT>& values,
- const Layout& layout);
-template <typename NativeT>
-XlaOp ConstantR4FromArray4D(XlaBuilder* builder,
- const Array4D<NativeT>& values);
-
-// Enqueues a rank one constant (XlaBuilder* builder, vector) onto the
-// computation. The vector has size 'length' and every element has the value
-// 'value'.
-template <typename NativeT>
-XlaOp ConstantR1(XlaBuilder* builder, int64 length, NativeT value);
-
-// Adds dimensions to an array by duplicating the data in the array.
-//
-// The new dimensions are inserted on the left, i.e. if
-// broadcast_sizes has values {a0, ..., aN} and the operand shape
-// has dimensions {b0, ..., bM} then the shape of the output has
-// dimensions {a0, ..., aN, b0, ..., bM}.
-//
-// The new dimensions index into copies of the operand, i.e.
-//
-// output[i0, ..., iN, j0, ..., jM] = operand[j0, ..., jM]
-XlaOp Broadcast(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> broadcast_sizes);
-
-// Performs in-dimension-style broadcast.
-//
-// Operand specifies the input to be broadcast. "shape" is expected output
-// shape. "broadcast_dimensions" are the dimensions to be broadcasting into.
-// Dimension numbers in broadcast_dimensions map to individual dimensions
-// of the operand, and specify what dimension of the output shape they
-// should be broadcast.
-// e.g.
-// Say operand = [1, 2], i.e., a 1D tensor with 2 elements.
-// and dimension of shape is [2,2].
-// Specifying {1} as brodcast_dimension will generate output
-// [1 , 2]
-// [1 , 2]
-// On the other hand, specifying {0} as broadcast_dimension
-// will generate output
-// [1 , 1]
-// [2 , 2]
-XlaOp BroadcastInDim(
- const XlaOp& operand, const Shape& shape,
- const tensorflow::gtl::ArraySlice<int64> broadcast_dimensions);
-
-// Enqueues a pad operation onto the computation that pads the given value on
-// the edges as well as between the elements of the input. padding_config
-// specifies the padding amount for each dimension.
-XlaOp Pad(const XlaOp& operand, const XlaOp& padding_value,
- const PaddingConfig& padding_config);
-
-// Enqueues an operation onto the computation that flattens the operand based
-// on the dimension order (major/slowest-varying to minor/fastest-varying)
-// given, followed by reshaping it into the shape with the given dimension
-// sizes (also major to minor). Conceptually, this is a limited form of
-// "shape casting".
-XlaOp Reshape(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> dimensions,
- tensorflow::gtl::ArraySlice<int64> new_sizes);
-
-// Enqueues an operation onto the computation that collapses the operand, from
-// first to last dimension (C order), then reshapes it to the given dimension
-// sizes. Conceptually, this is a limited form of "shape casting".
-XlaOp Reshape(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> new_sizes);
-
-// Wrapper for Reshape.
-// Enqueues an operation to collapse the provided dimensions; e.g. an
-// operand with dimensions {x=256, y=2, z=2, p=32} can be collapsed to
-// {x=1024, y=32} by collapsing dims {0, 1, 2}. Collapsing dimensions must
-// be a consecutive, in-order subsequence of the operand dimensions.
-//
-// Note that collapsing a single dimension does nothing:
-//
-// {256} collapsing {0} => {256}
-// {1} collapsing {0} => {1}
-//
-// Collapsing multiple dimensions produces a single result dimension:
-//
-// {256, 2} collapsing {0,1} => {512}
-// {256, 2, 3} collapsing {0,1} => {512, 3}
-//
-// This could potentially cause data to be moved -- it provides a more
-// structured form of reshaping than an arbitrary Reshape operation.
-XlaOp Collapse(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> dimensions);
-
-// Enqueues a slice operation onto the computation that slices the operand
-// from the start indices to the limit indices; e.g.
-//
-// x
-// [ 0 1 2 3 ]
-// y [ 4 5 6 7 ] => slice(start={1, 1}, limit={2, 3}) => [ 5 6 ]
-// [ 8 9 a b ]
-//
-// Note that "limit" means up-to-but-not-including; i.e. [start, limit) in 1D
-// range notation.
-// The strides parameter determines the stride over the slice
-XlaOp Slice(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> start_indices,
- tensorflow::gtl::ArraySlice<int64> limit_indices,
- tensorflow::gtl::ArraySlice<int64> strides);
-
-// Enqueues a slice operation in a given dimension, taking all other
-// dimensions as they are; e.g. if dimno is 1 from start_index 2 to
-// limit_index 4 by 1, and the shape is f32[7,8,9], this call is short-hand
-// for:
-//
-// array[:, 2:4:1, :]
-XlaOp SliceInDim(const XlaOp& operand, int64 start_index, int64 limit_index,
- int64 stride, int64 dimno);
-
-// Enqueues a slice operation onto the computation that slices the 'operand'
-// from dynamic start indices which are passed in 'start_indices'.
-// The size of the slice in each dimension is passed in 'slice_sizes',
-// which specify the end point of exclusive slice intervals in each
-// dimension [start, start + size).
-// The shape of 'start_indices' must be rank == 1, with dimension size
-// equal to the rank of the 'operand'.
-// Slice index calculations are computed modulo input dimension sizes to
-// prevent dynamic start indices from generating out-of-bound array accesses.
-XlaOp DynamicSlice(const XlaOp& operand, const XlaOp& start_indices,
- tensorflow::gtl::ArraySlice<int64> slice_sizes);
-
-// Enqueues a dynamic update slice operation onto the computation, which
-// updates a slice of 'operand' with 'update' at dynamic 'start_indices'.
-// The shape of 'update' determines the shape of the slice of 'operand'
-// which is updated.
-// The indices specified in 'start_indices' specify the offset of the slice
-// of 'operand' which is updated.
-//
-// update = {10, 11} // calculated at runtime.
-// [1 2 3] start = {1, 1} // calculated at runtime. [1 2 3 ]
-// [4 5 6] => DynamicUpdateslice(data, update, start) => [4 10 11]
-// [7 8 9] [7 8 9 ]
-//
-// The shape of 'start_indices' must be rank == 1, with dimension size
-// equal to the rank of the 'operand'.
-// Slice index calculations are computed modulo update dimension sizes to
-// prevent dynamic start indices from generating out-of-bound array accesses.
-XlaOp DynamicUpdateSlice(const XlaOp& operand, const XlaOp& update,
- const XlaOp& start_indices);
-
-// Enqueues a concatenate instruction onto the computation. 'operands' must
-// have >= 1 entry.
-XlaOp ConcatInDim(XlaBuilder* builder,
- tensorflow::gtl::ArraySlice<XlaOp> operands, int64 dimension);
-
-// Enqueue a tracing operation onto the computation; the computation will emit
-// a logging message with the operand.
-void Trace(const string& tag, const XlaOp& operand);
-
-// Enqueues a conditional-move-like select operation onto the computation;
-// predicated on pred, selects between on_true and on_false.
-XlaOp Select(const XlaOp& pred, const XlaOp& on_true, const XlaOp& on_false);
-
-// Enqueues a tuple-creation instruction onto the computation.
-XlaOp Tuple(XlaBuilder* builder, tensorflow::gtl::ArraySlice<XlaOp> elements);
-
-// Enqueues a tuple-element-get instruction onto the computation.
-XlaOp GetTupleElement(const XlaOp& tuple_data, int64 index);
-
-// Enqueues an equal-to comparison instruction onto the computation.
-XlaOp Eq(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-// Enqueues a not-equal comparison instruction onto the computation.
-XlaOp Ne(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-// Enqueues a greater-or-equal comparison instruction onto the computation.
-XlaOp Ge(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-// Enqueues a greater-than comparison instruction onto the computation.
-XlaOp Gt(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-// Enqueues a less-than comparison instruction onto the computation.
-XlaOp Lt(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-// Enqueues a less-or-equal comparison instruction onto the computation.
-XlaOp Le(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-// Enqueues a dot instruction onto the computation.
-XlaOp Dot(const XlaOp& lhs, const XlaOp& rhs);
-
-// Enqueues a general dot instruction onto the computation.
-XlaOp DotGeneral(const XlaOp& lhs, const XlaOp& rhs,
- const DotDimensionNumbers& dimension_numbers);
-
-// Enqueues a convolution instruction onto the computation, which uses the
-// default convolution dimension numbers.
-XlaOp Conv(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> window_strides, Padding padding);
-
-// Enqueues a convolution instruction onto the computation, with the caller
-// provided padding configuration in the format returned by MakePadding().
-XlaOp ConvWithGeneralPadding(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding);
-
-// Enqueues a convolution instruction onto the computation, with the caller
-// provided dimension numbers configuration.
-XlaOp ConvWithGeneralDimensions(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> window_strides, Padding padding,
- const ConvolutionDimensionNumbers& dimension_numbers);
-
-// Enqueues a convolution instruction onto the computation, with the caller
-// provided padding configuration as well as the dimension numbers.
-XlaOp ConvGeneral(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
- const ConvolutionDimensionNumbers& dimension_numbers);
-
-// Enqueues a convolution instruction onto the computation, with the caller
-// provided padding configuration, dilation factors and dimension numbers.
-XlaOp ConvGeneralDilated(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
- tensorflow::gtl::ArraySlice<int64> lhs_dilation,
- tensorflow::gtl::ArraySlice<int64> rhs_dilation,
- const ConvolutionDimensionNumbers& dimension_numbers);
-
-// Enqueues an FFT instruction onto the computation, of the given type and
-// with the given FFT length.
-XlaOp Fft(const XlaOp& operand, FftType fft_type,
- tensorflow::gtl::ArraySlice<int64> fft_length);
-
-// Enqueues an infeed instruction onto the computation, which writes data of
-// the given shape to the infeed buffer of the device.
-XlaOp Infeed(XlaBuilder* builder, const Shape& shape,
- const string& config = "");
-
-// Variant of Infeed which takes a token-shaped operand and produces a
-// two-element tuple containing the data value and a token-shaped value.
-// Tokens are used for ordering side-effecting operations.
-// TODO(b/110532604): Replace all uses of the non-token form with this variant.
-XlaOp InfeedWithToken(const XlaOp& token, const Shape& shape,
- const string& config = "");
-
-// Enqueues an outfeed instruction onto the computation. This instruction
-// generates outgoing data transfers for the given data.
-//
-// shape_with_layout communicates the laid out shape that we want to outfeed
-// -- if !ShapeUtil::Compatible(GetShape(operand), shape_with_layout) an error
-// will occur.
-void Outfeed(const XlaOp& operand, const Shape& shape_with_layout,
- const string& outfeed_config);
-
-// Variant of Outfeed which takes a token-shaped operand and produces a
-// token-shaped value. Tokens are used for ordering side-effecting operations.
-// TODO(b/110532604): Replace all uses of the non-token form with this variant.
-XlaOp OutfeedWithToken(const XlaOp& operand, const XlaOp& token,
- const Shape& shape_with_layout,
- const string& outfeed_config);
-
-// Enqueues a call instruction onto the computation.
-XlaOp Call(XlaBuilder* builder, const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<XlaOp> operands);
-
-// Enqueues a custom call instruction onto the computation.
-// During code generation, a call instruction is emitted which targets a
-// symbol with the name |call_target_name|. The |operands| are passed to the
-// call instruction. |shape| is the resultant shape.
-XlaOp CustomCall(XlaBuilder* builder, const string& call_target_name,
- tensorflow::gtl::ArraySlice<XlaOp> operands,
- const Shape& shape);
-
-// Enqueues a pseudo-op to represent host-side computation data-dependencies.
-// During code generation, host send and receive operations will be generated
-// to transfer |operands| to the host and a single result of |shape| back to
-// the device. Host send/recv operations are emitted using |channel_name|.
-// Dataflow dependencies and the |cost_estimate_ns| field may be used in HLO
-// instruction scheduling.
-XlaOp HostCompute(XlaBuilder* builder,
- tensorflow::gtl::ArraySlice<XlaOp> operands,
- const string& channel_name, int64 cost_estimate_ns,
- const Shape& shape);
-
-// The following methods enqueue element-wise binary arithmetic operations
-// onto the computation. The shapes of the operands have to match unless one
-// of the operands is a scalar, or an explicit broadcast dimension is given
-// (see g3doc for more details).
-
-// Enqueues a complex compose instruction onto the computation.
-XlaOp Complex(const XlaOp& real, const XlaOp& imag,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-// Enqueues a complex conjugate instruction onto the computation.
-XlaOp Conj(const XlaOp& operand);
-
-// Enqueues an add instruction onto the computation.
-XlaOp Add(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-// Enqueues a subtract instruction onto the computation.
-XlaOp Sub(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-// Enqueues a multiply instruction onto the computation.
-XlaOp Mul(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-// Enqueues a divide instruction onto the computation.
-XlaOp Div(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-// Enqueues a remainder instruction onto the computation.
-XlaOp Rem(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-// Enqueues a max instruction onto the computation.
-XlaOp Max(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-// Enqueues a min instruction onto the computation.
-XlaOp Min(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-// Element-wise logical operators
-XlaOp And(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-XlaOp Or(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-XlaOp Xor(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-XlaOp Not(const XlaOp& operand);
-
-XlaOp ShiftLeft(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-XlaOp ShiftRightArithmetic(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-XlaOp ShiftRightLogical(
- const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-// Reduces an array among the provided dimensions, given "computation" as a
-// reduction operator.
-XlaOp Reduce(const XlaOp& operand, const XlaOp& init_value,
- const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<int64> dimensions_to_reduce);
-
-// Convenience wrapper around the above that reduces all the dimensions in the
-// operand shape.
-XlaOp ReduceAll(const XlaOp& operand, const XlaOp& init_value,
- const XlaComputation& computation);
-
-// Enqueues a windowed reduce instruction onto the computation.
-XlaOp ReduceWindow(const XlaOp& operand, const XlaOp& init_value,
- const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<int64> window_dimensions,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- Padding padding);
-
-// As ReduceWindow(), but the padding is given in the format
-// returned by MakePadding().
-XlaOp ReduceWindowWithGeneralPadding(
- const XlaOp& operand, const XlaOp& init_value,
- const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<int64> window_dimensions,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding);
-
-// Returns the sum of the operand value within each subgroup of replicas. All
-// replicas supply one input to the sum and all replicas receive the resulting
-// sum for each subgroup.
-XlaOp CrossReplicaSum(
- const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> replica_group_ids = {});
-
-// Enqueues an operation that do an AllReduce of the operand cross cores. Here
-// AllReduce means doing a reduction on the input operand cross cores and then
-// broadcasting the reduction result to those cores. The reduction function is
-// defined by `computation`, which should be a commutative computation on
-// scalars, e.g., add, min, or max. The way that AllReduce is applied is
-// configured by:
-//
-// - `replica_group_ids`: maps replica ids to subgroup ids. If empty, all
-// replicas belong to one group. Allreduce will be applied within subgroups.
-// For example, we have 4 replicas, then replica_group_ids={0,1,0,1} means,
-// replica 0 and 2 are in subgroup 0, replica 1 and 3 are in subgroup 1.
-//
-// - `channel_id`: for Allreduce nodes from different models, if they have the
-// same channel_id, they will be 'Allreduce'd. If empty, Allreduce will not be
-// applied cross models.
-//
-// TODO(b/79737069): Rename this to AllReduce when it's ready to use.
-XlaOp CrossReplicaSum(const XlaOp& operand, const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<int64> replica_group_ids = {},
- const tensorflow::gtl::optional<ChannelHandle>&
- channel_id = tensorflow::gtl::nullopt);
-
-// Enqueues an operation that scatters the `source` array to the selected
-// indices of each window.
-XlaOp SelectAndScatter(const XlaOp& operand, const XlaComputation& select,
- tensorflow::gtl::ArraySlice<int64> window_dimensions,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- Padding padding, const XlaOp& source,
- const XlaOp& init_value, const XlaComputation& scatter);
-
-// As SelectAndScatter(), but the padding is given in the format
-// returned by MakePadding().
-XlaOp SelectAndScatterWithGeneralPadding(
- const XlaOp& operand, const XlaComputation& select,
- tensorflow::gtl::ArraySlice<int64> window_dimensions,
- tensorflow::gtl::ArraySlice<int64> window_strides,
- tensorflow::gtl::ArraySlice<std::pair<int64, int64>> padding,
- const XlaOp& source, const XlaOp& init_value,
- const XlaComputation& scatter);
-
-// Enqueues an abs instruction onto the computation.
-XlaOp Abs(const XlaOp& operand);
-
-// Enqueues a atan2 instruction onto the computation.
-XlaOp Atan2(const XlaOp& y, const XlaOp& x,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-// Enqueues an exp instruction onto the computation.
-XlaOp Exp(const XlaOp& operand);
-
-// Enqueues an expm1 instruction onto the computation.
-XlaOp Expm1(const XlaOp& operand);
-
-// Enqueues a floor instruction onto the computation.
-XlaOp Floor(const XlaOp& operand);
-
-// Enqueues a ceil instruction onto the computation.
-XlaOp Ceil(const XlaOp& operand);
-
-// Enqueues a round instruction onto the computation, rounding to nearest even
-// with half-way cases rounding away from zero.
-XlaOp Round(const XlaOp& operand);
-
-// Enqueues an log instruction (natural logarithm) onto the computation.
-XlaOp Log(const XlaOp& operand);
-
-// Enqueues an log1p instruction (log(x+1)) onto the computation.
-XlaOp Log1p(const XlaOp& operand);
-
-// Enqueues a sign instruction onto the computation.
-XlaOp Sign(const XlaOp& operand);
-
-// Enqueues a count leading zeros instruction onto the computation.
-XlaOp Clz(const XlaOp& operand);
-
-// Enqueues a cosine instruction onto the computation.
-XlaOp Cos(const XlaOp& operand);
-
-// Enqueues a sine instruction onto the computation.
-XlaOp Sin(const XlaOp& operand);
-
-// Enqueues a tanh instruction onto the computation.
-XlaOp Tanh(const XlaOp& operand);
-
-// Enqueues a real-part instruction onto the computation.
-XlaOp Real(const XlaOp& operand);
-
-// Enqueues an imaginary-part instruction onto the computation.
-XlaOp Imag(const XlaOp& operand);
-
-// Enqueues a lhs^rhs computation onto the computation.
-XlaOp Pow(const XlaOp& lhs, const XlaOp& rhs,
- tensorflow::gtl::ArraySlice<int64> broadcast_dimensions = {});
-
-// Enqueues an operator that tests if the operand's values are finite, i.e.,
-// not Inf or NaN. Defined only for floating-point types. Returns an array of
-// booleans with the same shape where entries are true iff the corresponding
-// entry was NaN.
-XlaOp IsFinite(const XlaOp& operand);
-
-// Enqueues a convert instruction onto the computation that changes the
-// element type of the operand array to primitive_type.
-XlaOp ConvertElementType(const XlaOp& operand, PrimitiveType new_element_type);
-
-// Enqueues a no-op instruction onto the computation that changes
-// the element type of the operand array to primitive_type. The
-// bit-widths of the source and destination element types must be
-// identical.
-XlaOp BitcastConvertType(const XlaOp& operand, PrimitiveType new_element_type);
-
-// Enqueues a negate instruction onto the computation.
-XlaOp Neg(const XlaOp& operand);
-
-// Enqueues a transpose instruction onto the computation.
-XlaOp Transpose(const XlaOp& operand,
- tensorflow::gtl::ArraySlice<int64> permutation);
-
-// Enqueues a reverse instruction onto the computation. The order of the
-// elements in the given dimensions is reversed (i.e., the element at index i
-// is moved to index dimension_size - 1 - i).
-XlaOp Rev(const XlaOp& operand, tensorflow::gtl::ArraySlice<int64> dimensions);
-
-// Enqueues a sort (as increasing order) instruction onto the computation.
-// If only keys are provided:
-// * If the keys are an rank-1 tensor (an array), the result is a sorted array
-// of keys, in ascending order.
-// * If the keys have higher rank, the keys are sorted along the provided
-// dimension. For example, for a rank-2 tensor (a matrix) of keys, a dimension
-// value of 0 will indepenently sort every column, and a dimension value of 1
-// will independently sort each row. If no dimension number is provided, then
-// the last dimension is chosen by default.
-//
-// If both keys and values are provided:
-// * The keys and the values must tensors with the same dimensions. The
-// element types of the tensors may be different.
-// * The result is a tuple that consists of a sorted tensor of keys (along the
-// provided dimension, as above) as the first element, and a tensor with their
-// corresponding values as the second element.
-XlaOp Sort(XlaOp keys,
- tensorflow::gtl::optional<XlaOp> values = tensorflow::gtl::nullopt,
- int64 dimension = -1);
-
-// Enqueues a clamp instruction onto the computation.
-XlaOp Clamp(const XlaOp& min, const XlaOp& operand, const XlaOp& max);
-
-// Enqueues a map instruction onto the computation.
-XlaOp Map(XlaBuilder* builder, tensorflow::gtl::ArraySlice<XlaOp> operands,
- const XlaComputation& computation,
- tensorflow::gtl::ArraySlice<int64> dimensions,
- tensorflow::gtl::ArraySlice<XlaOp> static_operands = {});
-
-// Enqueues a N(mu, sigma) random number generation instruction onto the
-// computation.
-XlaOp RngNormal(const XlaOp& mu, const XlaOp& sigma, const Shape& shape);
-
-// Enqueues a U(a, b) random number generation instruction onto the
-// computation. Returns values in the semi-open interval [a, b).
-XlaOp RngUniform(const XlaOp& a, const XlaOp& b, const Shape& shape);
-
-// Enqueues a while node onto the computation.
-XlaOp While(const XlaComputation& condition, const XlaComputation& body,
- const XlaOp& init);
-
-// Enqueues a conditional node onto the computation.
-XlaOp Conditional(const XlaOp& predicate, const XlaOp& true_operand,
- const XlaComputation& true_computation,
- const XlaOp& false_operand,
- const XlaComputation& false_computation);
-
-// Enqueues a ReducePrecision node onto the computation.
-XlaOp ReducePrecision(const XlaOp& operand, const int exponent_bits,
- const int mantissa_bits);
-
-// Enqueues a Gather node onto the computation.
-XlaOp Gather(const XlaOp& input, const XlaOp& gather_indices,
- const GatherDimensionNumbers& dimension_numbers,
- tensorflow::gtl::ArraySlice<int64> window_bounds);
-
-// Enqueues a Send node onto the computation for device-to-device
-// communication. This operation sends the given operand to
-// a Recv instruction in a different computation that shares the same channel
-// handle.
-void Send(const XlaOp& operand, const ChannelHandle& handle);
-
-// Variant of Send which takes a token-shaped operand and produces a
-// token-shaped value. Tokens are used for ordering side-effecting operations.
-// TODO(b/110532604): Replace all uses of the non-token form with this variant.
-XlaOp SendWithToken(const XlaOp& operand, const XlaOp& token,
- const ChannelHandle& handle);
-
-// Enqueues a Recv node onto the computation for device-to-device
-// communication. The data comes from a Send instruction in a different
-// computation that shares the same channel handle and its shape must be the
-// same as the given shape.
-XlaOp Recv(XlaBuilder* builder, const Shape& shape,
- const ChannelHandle& handle);
-
-// Variant of Recv which takes a token-shaped operand and produces a two-element
-// tuple containing the data value and a token-shaped value. Tokens are used
-// for ordering side-effecting operations.
-// TODO(b/110532604): Replace all uses of the non-token form with this variant.
-XlaOp RecvWithToken(const XlaOp& token, const Shape& shape,
- const ChannelHandle& handle);
-
-// Enqueues a Send node which transfers data from the device to the host. The
-// 'shape_with_layout' argument defines the layout of the data transferred; its
-// shape must be compatible with the shape of the operand. The operand must be
-// array-shaped.
-// TODO(b/111544877): Support tuple shapes.
-XlaOp SendToHost(const XlaOp& operand, const XlaOp& token,
- const Shape& shape_with_layout, const ChannelHandle& handle);
-
-// Enqueues a Recv node which transfers data from the host to the device. The
-// given shape must contain a layout and must be an array.
-// TODO(b/111544877): Support tuple shapes.
-XlaOp RecvFromHost(const XlaOp& token, const Shape& shape,
- const ChannelHandle& handle);
-
-// Enqueues an operation (AfterAll) with no operands that produces a
-// token-shaped value. Tokens are used for ordering side-effecting operations.
-// This is a separate method from AfterAll to facility the removal of
-// operand-less AfterAll instructions.
-// TODO(b/110532604): Remove this function when all tokens are derived from a
-// single token generated or passed into the entry computation.
-XlaOp CreateToken(XlaBuilder* builder);
-
-// Enqueues an AfterAll instruction which produces a token-shaped value and
-// takes a variadic number of token-shaped operands. The number of operands must
-// be greater than zero. Used for joining tokens.
-XlaOp AfterAll(XlaBuilder* builder, tensorflow::gtl::ArraySlice<XlaOp> tokens);
-
-// Normalizes operand across spatial and batch dimensions for each feature.
-//
-// Returns a tuple (normalized, batch_mean, batch_var) where `normalized`
-// is the normalized result and batch_mean and batch_var are the mean and
-// variance, respectively, across batch for the operand.
-XlaOp BatchNormTraining(const XlaOp& operand, const XlaOp& scale,
- const XlaOp& offset, float epsilon,
- int64 feature_index);
-
-// Normalizes operand across spatial and batch dimensions for each feature.
-//
-// `BatchNormInference` is equivalent to calling `BatchNormTraining` without
-// computing `mean` and `variance` for each batch inside the operation. It
-// uses the input `mean` and `variance` instead as estimated values. The
-// purpose of this op is to reduce latency in inference, hence the name
-// `BatchNormInference`.
-//
-// The output has the same shape as `operand`, and contains the normalized
-// values for each batch.
-XlaOp BatchNormInference(const XlaOp& operand, const XlaOp& scale,
- const XlaOp& offset, const XlaOp& mean,
- const XlaOp& variance, float epsilon,
- int64 feature_index);
-
-// Calculates the gradients of a batch norm op.
-//
-// The inputs `batch_mean` and `batch_var` represent the mean and variance
-// across the batch.
-//
-// Returns a tuple of three elements:
-// - grad_operand: Gradient with respect to input `operand`
-// - grad_offset: Gradient with respect to input `offset`
-// - grad_scale: Gradient with respect to input `scale`
-XlaOp BatchNormGrad(const XlaOp& operand, const XlaOp& scale,
- const XlaOp& batch_mean, const XlaOp& batch_var,
- const XlaOp& grad_output, float epsilon,
- int64 feature_index);
-
-// Implementation details below this point.
-
-template <typename NativeT>
-XlaOp XlaBuilder::ConstantR0(NativeT value) {
- return ConstantLiteral(*LiteralUtil::CreateR0<NativeT>(value));
-}
-
-template <typename NativeT>
-XlaOp XlaBuilder::ConstantR1(tensorflow::gtl::ArraySlice<NativeT> values) {
- return ConstantLiteral(*LiteralUtil::CreateR1<NativeT>(values));
-}
-
-template <typename NativeT>
-XlaOp XlaBuilder::ConstantR1(int64 length, NativeT value) {
- Literal literal(ShapeUtil::MakeShape(
- primitive_util::NativeToPrimitiveType<NativeT>(), {length}));
- literal.PopulateWithValue(value);
- return ConstantLiteral(literal);
-}
-
-inline XlaOp XlaBuilder::ConstantR1(const tensorflow::core::Bitmap& values) {
- return ConstantLiteral(*LiteralUtil::CreateR1(values));
-}
-
-template <typename NativeT>
-XlaOp XlaBuilder::ConstantR2(
- std::initializer_list<std::initializer_list<NativeT>> values) {
- return ConstantLiteral(*LiteralUtil::CreateR2<NativeT>(values));
-}
-
-template <typename NativeT>
-XlaOp XlaBuilder::ConstantFromArrayWithLayout(const Array<NativeT>& values,
- const Layout& layout) {
- return ConstantLiteral(
- *LiteralUtil::CreateFromArrayWithLayout<NativeT>(values, layout));
-}
-
-template <typename NativeT>
-XlaOp XlaBuilder::ConstantFromArray(const Array<NativeT>& values) {
- return ConstantLiteral(*LiteralUtil::CreateFromArray<NativeT>(values));
-}
-
-template <typename NativeT>
-XlaOp XlaBuilder::ConstantR2FromArray2DWithLayout(
- const Array2D<NativeT>& values, const Layout& layout) {
- return ConstantLiteral(
- *LiteralUtil::CreateFromArrayWithLayout<NativeT>(values, layout));
-}
-
-template <typename NativeT>
-XlaOp XlaBuilder::ConstantR2FromArray2D(const Array2D<NativeT>& values) {
- return ConstantLiteral(*LiteralUtil::CreateR2FromArray2D<NativeT>(values));
-}
-
-template <typename NativeT>
-XlaOp XlaBuilder::ConstantR3FromArray3DWithLayout(
- const Array3D<NativeT>& values, const Layout& layout) {
- return ConstantLiteral(
- *LiteralUtil::CreateR3FromArray3DWithLayout<NativeT>(values, layout));
-}
-
-template <typename NativeT>
-XlaOp XlaBuilder::ConstantR3FromArray3D(const Array3D<NativeT>& values) {
- return ConstantFromArray(values);
-}
-
-template <typename NativeT>
-XlaOp XlaBuilder::ConstantR4FromArray4DWithLayout(
- const Array4D<NativeT>& values, const Layout& layout) {
- return ConstantFromArrayWithLayout(values, layout);
-}
-
-template <typename NativeT>
-XlaOp XlaBuilder::ConstantR4FromArray4D(const Array4D<NativeT>& values) {
- return ConstantFromArray(values);
-}
-
-// Free function template implementations.
-
-template <typename NativeT>
-XlaOp ConstantR0(XlaBuilder* builder, NativeT value) {
- return ConstantLiteral(builder, *LiteralUtil::CreateR0<NativeT>(value));
-}
-
-template <typename NativeT>
-XlaOp ConstantR1(XlaBuilder* builder,
- tensorflow::gtl::ArraySlice<NativeT> values) {
- return ConstantLiteral(builder, *LiteralUtil::CreateR1<NativeT>(values));
-}
-
-template <typename NativeT>
-XlaOp ConstantR1(XlaBuilder* builder, int64 length, NativeT value) {
- Literal literal(ShapeUtil::MakeShape(
- primitive_util::NativeToPrimitiveType<NativeT>(), {length}));
- literal.PopulateWithValue(value);
- return ConstantLiteral(builder, literal);
-}
-
-inline XlaOp ConstantR1(XlaBuilder* builder,
- const tensorflow::core::Bitmap& values) {
- return ConstantLiteral(builder, *LiteralUtil::CreateR1(values));
-}
-
-template <typename NativeT>
-XlaOp ConstantR2(XlaBuilder* builder,
- std::initializer_list<std::initializer_list<NativeT>> values) {
- return ConstantLiteral(builder, *LiteralUtil::CreateR2<NativeT>(values));
-}
-
-template <typename NativeT>
-XlaOp ConstantFromArrayWithLayout(XlaBuilder* builder,
- const Array<NativeT>& values,
- const Layout& layout) {
- return ConstantLiteral(
- builder,
- *LiteralUtil::CreateFromArrayWithLayout<NativeT>(values, layout));
-}
-
-template <typename NativeT>
-XlaOp ConstantFromArray(XlaBuilder* builder, const Array<NativeT>& values) {
- return ConstantLiteral(builder,
- *LiteralUtil::CreateFromArray<NativeT>(values));
-}
-
-template <typename NativeT>
-XlaOp ConstantR2FromArray2DWithLayout(XlaBuilder* builder,
- const Array2D<NativeT>& values,
- const Layout& layout) {
- return ConstantLiteral(
- builder,
- *LiteralUtil::CreateFromArrayWithLayout<NativeT>(values, layout));
-}
-
-template <typename NativeT>
-XlaOp ConstantR2FromArray2D(XlaBuilder* builder,
- const Array2D<NativeT>& values) {
- return ConstantLiteral(builder,
- *LiteralUtil::CreateR2FromArray2D<NativeT>(values));
-}
-
-template <typename NativeT>
-XlaOp ConstantR3FromArray3DWithLayout(XlaBuilder* builder,
- const Array3D<NativeT>& values,
- const Layout& layout) {
- return ConstantLiteral(
- builder,
- *LiteralUtil::CreateR3FromArray3DWithLayout<NativeT>(values, layout));
-}
-
-template <typename NativeT>
-XlaOp ConstantR3FromArray3D(XlaBuilder* builder,
- const Array3D<NativeT>& values) {
- return ConstantFromArray(builder, values);
-}
-
-template <typename NativeT>
-XlaOp ConstantR4FromArray4DWithLayout(XlaBuilder* builder,
- const Array4D<NativeT>& values,
- const Layout& layout) {
- return ConstantFromArrayWithLayout(builder, values, layout);
-}
-
-template <typename NativeT>
-XlaOp ConstantR4FromArray4D(XlaBuilder* builder,
- const Array4D<NativeT>& values) {
- return ConstantFromArray(builder, values);
-}
-
-} // namespace xla
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#endif // TENSORFLOW_COMPILER_XLA_CLIENT_XLA_CLIENT_XLA_BUILDER_H_
diff --git a/tensorflow/compiler/xla/client/xla_client/xla_computation.cc b/tensorflow/compiler/xla/client/xla_computation.cc
index 72e3935696..3543d41fc2 100644
--- a/tensorflow/compiler/xla/client/xla_client/xla_computation.cc
+++ b/tensorflow/compiler/xla/client/xla_computation.cc
@@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include <utility>
diff --git a/tensorflow/compiler/xla/client/xla_client/xla_computation.h b/tensorflow/compiler/xla/client/xla_computation.h
index 0ffba208b1..71598ef8b2 100644
--- a/tensorflow/compiler/xla/client/xla_client/xla_computation.h
+++ b/tensorflow/compiler/xla/client/xla_computation.h
@@ -13,8 +13,8 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
-#ifndef TENSORFLOW_COMPILER_XLA_CLIENT_XLA_CLIENT_XLA_COMPUTATION_H_
-#define TENSORFLOW_COMPILER_XLA_CLIENT_XLA_CLIENT_XLA_COMPUTATION_H_
+#ifndef TENSORFLOW_COMPILER_XLA_CLIENT_XLA_COMPUTATION_H_
+#define TENSORFLOW_COMPILER_XLA_CLIENT_XLA_COMPUTATION_H_
#include <utility>
@@ -64,4 +64,4 @@ class XlaComputation {
} // namespace xla
-#endif // TENSORFLOW_COMPILER_XLA_CLIENT_XLA_CLIENT_XLA_COMPUTATION_H_
+#endif // TENSORFLOW_COMPILER_XLA_CLIENT_XLA_COMPUTATION_H_
diff --git a/tensorflow/compiler/xla/experimental/xla_sharding/xla_sharding.py b/tensorflow/compiler/xla/experimental/xla_sharding/xla_sharding.py
index abd10b164e..fb135f5ced 100644
--- a/tensorflow/compiler/xla/experimental/xla_sharding/xla_sharding.py
+++ b/tensorflow/compiler/xla/experimental/xla_sharding/xla_sharding.py
@@ -20,7 +20,7 @@ from __future__ import print_function
import math
-import numpy as np
+import numpy as _np # Avoids becoming a part of public Tensorflow API.
from tensorflow.compiler.xla import xla_data_pb2
from tensorflow.compiler.xla.python_api import xla_shape
@@ -85,7 +85,7 @@ class Sharding(object):
something we really want to expose to users (especially as the
contract for tile_assignment is very strict).
"""
- if not isinstance(tile_assignment, np.ndarray):
+ if not isinstance(tile_assignment, _np.ndarray):
raise TypeError('Tile assignment must be of type np.ndarray')
if not isinstance(tile_shape, xla_shape.Shape):
raise TypeError('Tile shape must be of type xla_shape.Shape')
diff --git a/tensorflow/compiler/xla/python/BUILD b/tensorflow/compiler/xla/python/BUILD
index fe346f9956..c8f2d65c22 100644
--- a/tensorflow/compiler/xla/python/BUILD
+++ b/tensorflow/compiler/xla/python/BUILD
@@ -53,9 +53,9 @@ cc_library(
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:executable_build_options",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:math",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/compiler/xla/service:shaped_buffer",
"//tensorflow/core:framework_lite",
"//tensorflow/core:lib",
diff --git a/tensorflow/compiler/xla/python/local_computation_builder.cc b/tensorflow/compiler/xla/python/local_computation_builder.cc
index 8aefc4cd5e..8246f76d34 100644
--- a/tensorflow/compiler/xla/python/local_computation_builder.cc
+++ b/tensorflow/compiler/xla/python/local_computation_builder.cc
@@ -15,7 +15,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/python/local_computation_builder.h"
#include "tensorflow/compiler/xla/client/lib/math.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/executable_run_options.h"
#include "tensorflow/compiler/xla/ptr_util.h"
#include "tensorflow/compiler/xla/util.h"
@@ -624,6 +624,7 @@ _FORWARD_BINOP(ShiftRightArithmetic)
_FORWARD_BINOP(ShiftRightLogical)
_FORWARD_BINOP(Atan2)
_FORWARD_BINOP(Pow)
+_FORWARD_BINOP(Complex)
_FORWARD_UNOP(Not)
_FORWARD_UNOP(Abs)
_FORWARD_UNOP(Exp)
@@ -658,6 +659,9 @@ _FORWARD_UNOP(Asinh)
_FORWARD_UNOP(Atanh)
_FORWARD_UNOP(Cosh)
_FORWARD_UNOP(Sinh)
+_FORWARD_UNOP(Real)
+_FORWARD_UNOP(Imag)
+_FORWARD_UNOP(Conj)
#undef _FORWARD
#undef _FORWARD_UNOP
diff --git a/tensorflow/compiler/xla/python/local_computation_builder.h b/tensorflow/compiler/xla/python/local_computation_builder.h
index dd9e2fbe72..a568c24c63 100644
--- a/tensorflow/compiler/xla/python/local_computation_builder.h
+++ b/tensorflow/compiler/xla/python/local_computation_builder.h
@@ -19,8 +19,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/client_library.h"
#include "tensorflow/compiler/xla/client/executable_build_options.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/service/shaped_buffer.h"
#include "tensorflow/compiler/xla/xla_data.pb.h"
#include "tensorflow/core/lib/gtl/array_slice.h"
@@ -341,6 +341,7 @@ class LocalComputationBuilder {
_FORWARD_BINOP(ShiftRightLogical)
_FORWARD_BINOP(Atan2)
_FORWARD_BINOP(Pow)
+ _FORWARD_BINOP(Complex)
_FORWARD_UNOP(Not)
_FORWARD_UNOP(Abs)
_FORWARD_UNOP(Exp)
@@ -375,6 +376,9 @@ class LocalComputationBuilder {
_FORWARD_UNOP(Atanh)
_FORWARD_UNOP(Cosh)
_FORWARD_UNOP(Sinh)
+ _FORWARD_UNOP(Real)
+ _FORWARD_UNOP(Imag)
+ _FORWARD_UNOP(Conj)
#undef _FORWARD
#undef _FORWARD_UNOP
diff --git a/tensorflow/compiler/xla/python/local_computation_builder.i b/tensorflow/compiler/xla/python/local_computation_builder.i
index 9b8b0aa7f2..5d5a955bfe 100644
--- a/tensorflow/compiler/xla/python/local_computation_builder.i
+++ b/tensorflow/compiler/xla/python/local_computation_builder.i
@@ -1029,6 +1029,10 @@ tensorflow::ImportNumpy();
%unignore xla::swig::LocalComputationBuilder::Atanh;
%unignore xla::swig::LocalComputationBuilder::Cosh;
%unignore xla::swig::LocalComputationBuilder::Sinh;
+%unignore xla::swig::LocalComputationBuilder::Real;
+%unignore xla::swig::LocalComputationBuilder::Imag;
+%unignore xla::swig::LocalComputationBuilder::Conj;
+%unignore xla::swig::LocalComputationBuilder::Complex;
%unignore xla::swig::DestructureLocalShapedBufferTuple;
%unignore xla::swig::DeleteLocalShapedBuffer;
%unignore xla::swig::DeleteLocalComputation;
diff --git a/tensorflow/compiler/xla/python/numpy_bridge.cc b/tensorflow/compiler/xla/python/numpy_bridge.cc
index 71351abd59..6f665faf61 100644
--- a/tensorflow/compiler/xla/python/numpy_bridge.cc
+++ b/tensorflow/compiler/xla/python/numpy_bridge.cc
@@ -50,6 +50,8 @@ int PrimitiveTypeToNumpyType(PrimitiveType primitive_type) {
return NPY_FLOAT32;
case F64:
return NPY_FLOAT64;
+ case C64:
+ return NPY_COMPLEX64;
case TUPLE:
return NPY_OBJECT;
default:
@@ -83,6 +85,8 @@ PrimitiveType NumpyTypeToPrimitiveType(int np_type) {
return F32;
case NPY_FLOAT64:
return F64;
+ case NPY_COMPLEX64:
+ return C64;
case NPY_OBJECT:
return TUPLE;
default:
@@ -104,6 +108,7 @@ bool NumpyTypeIsValid(int np_type) {
case NPY_FLOAT16:
case NPY_FLOAT32:
case NPY_FLOAT64:
+ case NPY_COMPLEX64:
case NPY_OBJECT:
return true;
default:
@@ -425,6 +430,9 @@ Status CopyNumpyArrayToLiteral(int np_type, PyArrayObject* py_array,
case NPY_FLOAT64:
CopyNumpyArrayToLiteral<double>(py_array, literal);
break;
+ case NPY_COMPLEX64:
+ CopyNumpyArrayToLiteral<complex64>(py_array, literal);
+ break;
default:
return InvalidArgument(
"No XLA literal container for Numpy type number: %d", np_type);
@@ -462,6 +470,9 @@ void CopyLiteralToNumpyArray(int np_type, const LiteralSlice& literal,
case NPY_FLOAT64:
CopyLiteralToNumpyArray<double>(literal, py_array);
break;
+ case NPY_COMPLEX64:
+ CopyLiteralToNumpyArray<complex64>(literal, py_array);
+ break;
default:
LOG(FATAL) << "No XLA literal container for Numpy type" << np_type;
}
diff --git a/tensorflow/compiler/xla/python/xla_client.py b/tensorflow/compiler/xla/python/xla_client.py
index c0105b385b..a2c6fc344d 100644
--- a/tensorflow/compiler/xla/python/xla_client.py
+++ b/tensorflow/compiler/xla/python/xla_client.py
@@ -120,6 +120,9 @@ _UNARY_OPS = [
'Atanh',
'Cosh',
'Sinh',
+ 'Real',
+ 'Imag',
+ 'Conj',
]
_BINARY_OPS = [
@@ -144,6 +147,7 @@ _BINARY_OPS = [
'ShiftRightArithmetic',
'ShiftRightLogical',
'Atan2',
+ 'Complex',
]
diff --git a/tensorflow/compiler/xla/python_api/types.py b/tensorflow/compiler/xla/python_api/types.py
index b60f8dce92..e036c894f8 100644
--- a/tensorflow/compiler/xla/python_api/types.py
+++ b/tensorflow/compiler/xla/python_api/types.py
@@ -20,7 +20,7 @@ from __future__ import print_function
import collections
-import numpy as np
+import numpy as _np # Avoids becoming a part of public Tensorflow API.
from tensorflow.compiler.xla import xla_data_pb2
@@ -43,73 +43,73 @@ MAP_XLA_TYPE_TO_RECORD = {
xla_data_pb2.F16:
TypeConversionRecord(
primitive_type=xla_data_pb2.F16,
- numpy_dtype=np.float16,
+ numpy_dtype=_np.float16,
literal_field_name='f16s',
literal_field_type=float),
xla_data_pb2.F32:
TypeConversionRecord(
primitive_type=xla_data_pb2.F32,
- numpy_dtype=np.float32,
+ numpy_dtype=_np.float32,
literal_field_name='f32s',
literal_field_type=float),
xla_data_pb2.F64:
TypeConversionRecord(
primitive_type=xla_data_pb2.F64,
- numpy_dtype=np.float64,
+ numpy_dtype=_np.float64,
literal_field_name='f64s',
literal_field_type=float),
xla_data_pb2.S8:
TypeConversionRecord(
primitive_type=xla_data_pb2.S8,
- numpy_dtype=np.int8,
+ numpy_dtype=_np.int8,
literal_field_name='s8s',
literal_field_type=int),
xla_data_pb2.S16:
TypeConversionRecord(
primitive_type=xla_data_pb2.S16,
- numpy_dtype=np.int16,
+ numpy_dtype=_np.int16,
literal_field_name='s16s',
literal_field_type=int),
xla_data_pb2.S32:
TypeConversionRecord(
primitive_type=xla_data_pb2.S32,
- numpy_dtype=np.int32,
+ numpy_dtype=_np.int32,
literal_field_name='s32s',
literal_field_type=int),
xla_data_pb2.S64:
TypeConversionRecord(
primitive_type=xla_data_pb2.S64,
- numpy_dtype=np.int64,
+ numpy_dtype=_np.int64,
literal_field_name='s64s',
literal_field_type=int),
xla_data_pb2.U8:
TypeConversionRecord(
primitive_type=xla_data_pb2.U8,
- numpy_dtype=np.uint8,
+ numpy_dtype=_np.uint8,
literal_field_name='s8s',
literal_field_type=int),
xla_data_pb2.U16:
TypeConversionRecord(
primitive_type=xla_data_pb2.U16,
- numpy_dtype=np.uint16,
+ numpy_dtype=_np.uint16,
literal_field_name='s16s',
literal_field_type=int),
xla_data_pb2.U32:
TypeConversionRecord(
primitive_type=xla_data_pb2.U32,
- numpy_dtype=np.uint32,
+ numpy_dtype=_np.uint32,
literal_field_name='s32s',
literal_field_type=int),
xla_data_pb2.U64:
TypeConversionRecord(
primitive_type=xla_data_pb2.U64,
- numpy_dtype=np.uint64,
+ numpy_dtype=_np.uint64,
literal_field_name='s64s',
literal_field_type=int),
xla_data_pb2.PRED:
TypeConversionRecord(
primitive_type=xla_data_pb2.PRED,
- numpy_dtype=np.bool,
+ numpy_dtype=_np.bool,
literal_field_name='preds',
literal_field_type=bool)
}
@@ -119,6 +119,6 @@ MAP_XLA_TYPE_TO_RECORD = {
# doesn't work as expected (https://github.com/numpy/numpy/issues/7242). Thus,
# when keying by dtype in this dict, we use the string form of dtypes.
MAP_DTYPE_TO_RECORD = {
- str(np.dtype(record.numpy_dtype)): record
+ str(_np.dtype(record.numpy_dtype)): record
for record in MAP_XLA_TYPE_TO_RECORD.values()
}
diff --git a/tensorflow/compiler/xla/python_api/xla_literal.py b/tensorflow/compiler/xla/python_api/xla_literal.py
index b040098c29..757e41a78a 100644
--- a/tensorflow/compiler/xla/python_api/xla_literal.py
+++ b/tensorflow/compiler/xla/python_api/xla_literal.py
@@ -18,7 +18,7 @@ from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
-import numpy as np
+import numpy as _np # Avoids becoming a part of public Tensorflow API.
from tensorflow.compiler.xla import xla_data_pb2
from tensorflow.compiler.xla.python_api import types
@@ -35,7 +35,7 @@ def ConvertLiteralToNumpyArray(literal):
type_record = types.MAP_XLA_TYPE_TO_RECORD[element_type]
if not literal.shape.dimensions:
- return np.array(
+ return _np.array(
getattr(literal, type_record.literal_field_name)[0],
type_record.numpy_dtype)
else:
@@ -54,7 +54,7 @@ def ConvertLiteralToNumpyArray(literal):
numpy_reshaper = lambda arr: arr.reshape(numpy_shape, order='C')
else:
raise NotImplementedError('Unsupported layout: {0}'.format(layout_order))
- ndarray = np.array(
+ ndarray = _np.array(
getattr(literal, type_record.literal_field_name),
copy=False,
dtype=type_record.numpy_dtype)
@@ -69,11 +69,11 @@ def _ConvertNumpyArrayToLiteral(ndarray):
if ndarray.ndim == 0:
getattr(literal, type_record.literal_field_name).append(
- np.asscalar(ndarray.astype(type_record.literal_field_type)))
+ _np.asscalar(ndarray.astype(type_record.literal_field_type)))
else:
# Ndarrays with boolean dtypes need special type conversion with protobufs
- if ndarray.dtype in {np.bool_, np.dtype('bool')}:
- for element in np.nditer(ndarray):
+ if ndarray.dtype in {_np.bool_, _np.dtype('bool')}:
+ for element in _np.nditer(ndarray):
getattr(literal, type_record.literal_field_name).append(
type_record.literal_field_type(element))
else:
diff --git a/tensorflow/compiler/xla/python_api/xla_shape.py b/tensorflow/compiler/xla/python_api/xla_shape.py
index 6af2895803..f158f6b241 100644
--- a/tensorflow/compiler/xla/python_api/xla_shape.py
+++ b/tensorflow/compiler/xla/python_api/xla_shape.py
@@ -18,7 +18,7 @@ from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
-import numpy as np
+import numpy as _np # Avoids becoming a part of public Tensorflow API.
from tensorflow.compiler.xla import xla_data_pb2
from tensorflow.compiler.xla.python_api import types
@@ -111,7 +111,7 @@ def _CreateShapeFromNumpy(ndarray): # pylint: disable=invalid-name
# Set the shape's layout based on the ordering of ndarray.
# Numpy arrays come in two orders: Fortran (column-major) and C (row-major).
- if np.isfortran(ndarray):
+ if _np.isfortran(ndarray):
# Column-major layout. This corresponds to a "dimension order is
# minor-to-major" layout in XLA.
layout = range(ndarray.ndim)
diff --git a/tensorflow/compiler/xla/reference_util.cc b/tensorflow/compiler/xla/reference_util.cc
index 6397f1f479..a803520876 100644
--- a/tensorflow/compiler/xla/reference_util.cc
+++ b/tensorflow/compiler/xla/reference_util.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include <array>
#include <utility>
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/compiler/xla/service/cpu/runtime_single_threaded_matmul.h"
#include "tensorflow/compiler/xla/service/hlo_evaluator.h"
diff --git a/tensorflow/compiler/xla/rpc/BUILD b/tensorflow/compiler/xla/rpc/BUILD
index 0b1cec1925..44b22a5586 100644
--- a/tensorflow/compiler/xla/rpc/BUILD
+++ b/tensorflow/compiler/xla/rpc/BUILD
@@ -56,7 +56,7 @@ tf_cc_test(
":grpc_stub",
"//tensorflow:grpc++",
"//tensorflow/compiler/xla/client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/core:framework_internal",
"//tensorflow/core:lib",
diff --git a/tensorflow/compiler/xla/rpc/grpc_client_test.cc b/tensorflow/compiler/xla/rpc/grpc_client_test.cc
index 90efee50b4..6788676181 100644
--- a/tensorflow/compiler/xla/rpc/grpc_client_test.cc
+++ b/tensorflow/compiler/xla/rpc/grpc_client_test.cc
@@ -24,7 +24,7 @@ limitations under the License.
#include "grpcpp/security/credentials.h"
#include "tensorflow/compiler/xla/client/client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/rpc/grpc_stub.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
#include "tensorflow/core/lib/io/path.h"
diff --git a/tensorflow/compiler/xla/service/BUILD b/tensorflow/compiler/xla/service/BUILD
index cba7883fde..528b7fdfd3 100644
--- a/tensorflow/compiler/xla/service/BUILD
+++ b/tensorflow/compiler/xla/service/BUILD
@@ -256,7 +256,7 @@ tf_cc_test(
"//tensorflow/compiler/xla:types",
"//tensorflow/compiler/xla:util",
"//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/service:hlo_element_type_converter",
"//tensorflow/compiler/xla/tests:hlo_verified_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
@@ -564,7 +564,7 @@ cc_library(
":computation_placer",
":device_memory_allocator",
":platform_util",
- ":pool",
+ ":stream_pool",
":transfer_manager",
"//tensorflow/compiler/xla:status_macros",
"//tensorflow/compiler/xla:statusor",
@@ -598,6 +598,7 @@ cc_library(
":hlo_proto_util",
":platform_util",
":source_map_util",
+ ":stream_pool",
":transfer_manager",
"//tensorflow/compiler/xla:executable_run_options",
"//tensorflow/compiler/xla:execution_options_util",
@@ -642,7 +643,7 @@ cc_library(
"//tensorflow/compiler/xla:util",
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:executable_build_options",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/core:lib",
"//tensorflow/core:stream_executor_no_cuda",
],
@@ -751,8 +752,8 @@ cc_library(
":hlo_execution_profile",
":hlo_graph_dumper",
":hlo_proto",
- ":pool",
":shaped_buffer",
+ ":stream_pool",
"//tensorflow/compiler/xla:executable_run_options",
"//tensorflow/compiler/xla:status",
"//tensorflow/compiler/xla:status_macros",
@@ -838,7 +839,7 @@ cc_library(
hdrs = ["execution_tracker.h"],
deps = [
":backend",
- ":pool",
+ ":stream_pool",
"//tensorflow/compiler/xla:executable_run_options",
"//tensorflow/compiler/xla:statusor",
"//tensorflow/compiler/xla:util",
@@ -946,7 +947,6 @@ cc_library(
"//tensorflow/compiler/xla:statusor",
"//tensorflow/compiler/xla:types",
"//tensorflow/compiler/xla:util",
- "//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/core:lib",
"//tensorflow/core:lib_internal",
],
@@ -1663,8 +1663,8 @@ tf_cc_test(
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:local_client",
"//tensorflow/compiler/xla/client:padding",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:hlo_test_base",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
"//tensorflow/core:lib",
@@ -2670,7 +2670,7 @@ tf_cc_test(
"//tensorflow/compiler/xla:test",
"//tensorflow/compiler/xla:test_helpers",
"//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/service:hlo_parser",
"//tensorflow/compiler/xla/service/gpu:ir_emission_utils",
"//tensorflow/compiler/xla/tests:hlo_test_base",
@@ -2707,7 +2707,7 @@ tf_cc_test(
"//tensorflow/compiler/xla:test",
"//tensorflow/compiler/xla:test_helpers",
"//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/tests:hlo_test_base",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
"//tensorflow/core:lib",
@@ -2715,21 +2715,25 @@ tf_cc_test(
)
cc_library(
- name = "pool",
- hdrs = ["pool.h"],
+ name = "stream_pool",
+ srcs = ["stream_pool.cc"],
+ hdrs = ["stream_pool.h"],
deps = [
+ "//tensorflow/compiler/xla:types",
"//tensorflow/compiler/xla:util",
"//tensorflow/core:lib",
+ "//tensorflow/core:stream_executor_no_cuda",
],
)
tf_cc_test(
- name = "pool_test",
- srcs = ["pool_test.cc"],
+ name = "stream_pool_test",
+ srcs = ["stream_pool_test.cc"],
deps = [
- ":pool",
+ ":stream_pool",
"//tensorflow/compiler/xla:test_helpers",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
+ "//tensorflow/core:stream_executor_no_cuda",
],
)
diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier.cc b/tensorflow/compiler/xla/service/algebraic_simplifier.cc
index 26a8a67601..946ef6f0d6 100644
--- a/tensorflow/compiler/xla/service/algebraic_simplifier.cc
+++ b/tensorflow/compiler/xla/service/algebraic_simplifier.cc
@@ -150,6 +150,8 @@ class AlgebraicSimplifierVisitor : public DfsHloVisitorWithDefault {
Status HandleDynamicUpdateSlice(
HloInstruction* dynamic_update_slice) override;
+ Status HandleSort(HloInstruction* sort) override;
+
Status HandleTranspose(HloInstruction* transpose) override;
Status HandleSubtract(HloInstruction* sub) override;
@@ -1769,13 +1771,12 @@ Status AlgebraicSimplifierVisitor::HandleSlice(HloInstruction* slice) {
Status AlgebraicSimplifierVisitor::HandleDynamicSlice(
HloInstruction* dynamic_slice) {
auto operand = dynamic_slice->mutable_operand(0);
- auto start_indices = dynamic_slice->operand(1);
if (ShapeUtil::IsScalar(dynamic_slice->shape())) {
return ReplaceInstruction(dynamic_slice, operand);
}
- // DynamicSlice where operand has the same size as the output and
- // start_indices are all zero is simply equal to operand.
- if (IsAll(start_indices, 0) && SameShape(operand, dynamic_slice)) {
+ // DynamicSlice where operand has the same size as the output is simply equal
+ // to operand.
+ if (SameShape(operand, dynamic_slice)) {
return ReplaceInstruction(dynamic_slice, operand);
}
return Status::OK();
@@ -1784,20 +1785,10 @@ Status AlgebraicSimplifierVisitor::HandleDynamicSlice(
Status AlgebraicSimplifierVisitor::HandleDynamicUpdateSlice(
HloInstruction* dynamic_update_slice) {
auto update = dynamic_update_slice->mutable_operand(1);
- auto start_indices = dynamic_update_slice->operand(2);
- // DynamicUpdateSlice on a scalar just passes through the update argument.
- if (ShapeUtil::IsScalar(dynamic_update_slice->shape())) {
- return ReplaceInstruction(dynamic_update_slice, update);
- }
- // DynamicUpdateSlice where operand and update have the same size and
- // start_indices are all zero is simply equal to update.
- //
- // (We require start_indices to be all zero because we want this optimization
- // not to affect the visible behavior of this op even when the indices are out
- // of range. Currently dynamic-update-slice wraps out-of-range indices, so
- // we can only remove the op if its indices never wrap.)
- if (IsAll(start_indices, 0) && SameShape(dynamic_update_slice, update)) {
+ // DynamicUpdateSlice where operand and update have the same size is simply
+ // equal to update.
+ if (SameShape(dynamic_update_slice, update)) {
return ReplaceInstruction(dynamic_update_slice, update);
}
@@ -2116,6 +2107,21 @@ Status AlgebraicSimplifierVisitor::HandleReduceWindow(
/*reduce_computation=*/function));
}
+Status AlgebraicSimplifierVisitor::HandleSort(HloInstruction* sort) {
+ auto operand = sort->mutable_operand(0);
+ int64 dimension_to_sort = sort->dimensions(0);
+ if (ShapeUtil::IsZeroElementArray(operand->shape()) ||
+ operand->shape().dimensions(dimension_to_sort) <= 1) {
+ if (sort->operand_count() == 1) {
+ return ReplaceInstruction(sort, operand);
+ }
+ // If it is key/value sort, the output of sort is a tuple.
+ return ReplaceWithNewInstruction(
+ sort, HloInstruction::CreateTuple({operand, sort->mutable_operand(1)}));
+ }
+ return Status::OK();
+}
+
Status AlgebraicSimplifierVisitor::HandleTranspose(HloInstruction* transpose) {
auto operand = transpose->mutable_operand(0);
if (std::is_sorted(transpose->dimensions().begin(),
diff --git a/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc b/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc
index ddf0a513c0..ad14fe6f2c 100644
--- a/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc
+++ b/tensorflow/compiler/xla/service/algebraic_simplifier_test.cc
@@ -1941,6 +1941,40 @@ TEST_F(AlgebraicSimplifierTest, SliceOfSliceToSlice) {
EXPECT_EQ(computation->root_instruction()->slice_limits(1), dim1 - 4);
}
+TEST_F(AlgebraicSimplifierTest, RemoveNoopSort) {
+ auto builder = HloComputation::Builder(TestName());
+
+ Shape keys_shape = ShapeUtil::MakeShape(F32, {1});
+ auto keys = builder.AddInstruction(
+ HloInstruction::CreateParameter(0, keys_shape, "keys"));
+ builder.AddInstruction(HloInstruction::CreateSort(keys_shape, 0, keys));
+ auto module = CreateNewModule();
+ HloComputation* computation = module->AddEntryComputation(builder.Build());
+ AlgebraicSimplifier simplifier(/*is_layout_sensitive=*/false,
+ non_bitcasting_callback());
+ ASSERT_TRUE(simplifier.Run(module).ValueOrDie());
+ EXPECT_THAT(computation->root_instruction(), keys);
+}
+
+TEST_F(AlgebraicSimplifierTest, ReplaceEffectiveScalarKeyValueSortWithTuple) {
+ auto builder = HloComputation::Builder(TestName());
+
+ Shape keys_shape = ShapeUtil::MakeShape(F32, {5, 0});
+ Shape values_shape = ShapeUtil::MakeShape(S32, {5, 0});
+ auto keys = builder.AddInstruction(
+ HloInstruction::CreateParameter(0, keys_shape, "keys"));
+ auto values = builder.AddInstruction(
+ HloInstruction::CreateParameter(1, values_shape, "values"));
+ builder.AddInstruction(HloInstruction::CreateSort(
+ ShapeUtil::MakeTupleShape({keys_shape, values_shape}), 0, keys, values));
+ auto module = CreateNewModule();
+ HloComputation* computation = module->AddEntryComputation(builder.Build());
+ AlgebraicSimplifier simplifier(/*is_layout_sensitive=*/false,
+ non_bitcasting_callback());
+ ASSERT_TRUE(simplifier.Run(module).ValueOrDie());
+ EXPECT_THAT(computation->root_instruction(), op::Tuple(keys, values));
+}
+
TEST_F(AlgebraicSimplifierTest, ConvertConvToMatmul) {
struct ConvTestOptions {
int in_batch = 10;
@@ -2489,8 +2523,8 @@ TEST_F(AlgebraicSimplifierTest, TrivialDynamicSlice) {
shape,
builder.AddInstruction(
HloInstruction::CreateParameter(0, shape, "slice_from")),
- builder.AddInstruction(HloInstruction::CreateConstant(
- LiteralUtil::CreateR1<int>({0, 0, 0}))),
+ builder.AddInstruction(HloInstruction::CreateParameter(
+ 1, ShapeUtil::MakeShape(U32, {3}), "slice_indices")),
/*slice_sizes=*/{10, 100, 1000}));
auto computation = module().AddEntryComputation(builder.Build());
@@ -2523,8 +2557,8 @@ TEST_F(AlgebraicSimplifierTest, TrivialDynamicUpdateSlice) {
builder.AddInstruction(
HloInstruction::CreateParameter(2, slice_shape, "to_update")),
slice,
- builder.AddInstruction(HloInstruction::CreateConstant(
- LiteralUtil::CreateR1<int>({0, 0, 0})))));
+ builder.AddInstruction(HloInstruction::CreateParameter(
+ 3, ShapeUtil::MakeShape(U32, {3}), "update_indices"))));
auto computation = module().AddEntryComputation(builder.Build());
AlgebraicSimplifier simplifier(/*is_layout_sensitive=*/false,
diff --git a/tensorflow/compiler/xla/service/backend.cc b/tensorflow/compiler/xla/service/backend.cc
index 349b32451a..d12be3e007 100644
--- a/tensorflow/compiler/xla/service/backend.cc
+++ b/tensorflow/compiler/xla/service/backend.cc
@@ -96,24 +96,19 @@ Backend::CreateDefaultBackend() {
return CreateBackend(backend_options);
}
-StatusOr<Backend::StreamPtr> Backend::BorrowStream(int device_ordinal) {
- TF_ASSIGN_OR_RETURN(auto exec, stream_executor(device_ordinal));
- return BorrowStream(exec);
+StatusOr<StreamPool::Ptr> Backend::BorrowStream(int device_ordinal) {
+ TF_ASSIGN_OR_RETURN(auto executor, stream_executor(device_ordinal));
+ return BorrowStream(executor);
}
-StatusOr<Backend::StreamPtr> Backend::BorrowStream(
- se::StreamExecutor* executor) {
+StatusOr<StreamPool::Ptr> Backend::BorrowStream(se::StreamExecutor* executor) {
tensorflow::mutex_lock l(mu_);
if (0 == stream_pools_.count(executor)) {
stream_pools_.emplace(std::piecewise_construct,
std::forward_as_tuple(executor),
- std::forward_as_tuple([executor]() {
- auto stream = MakeUnique<se::Stream>(executor);
- stream->Init();
- return stream;
- }));
+ std::forward_as_tuple());
}
- return stream_pools_.at(executor).Allocate();
+ return stream_pools_.at(executor).BorrowStream(executor);
}
Backend::Backend(
diff --git a/tensorflow/compiler/xla/service/backend.h b/tensorflow/compiler/xla/service/backend.h
index 6546602473..1bc3796fa4 100644
--- a/tensorflow/compiler/xla/service/backend.h
+++ b/tensorflow/compiler/xla/service/backend.h
@@ -24,7 +24,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/service/compiler.h"
#include "tensorflow/compiler/xla/service/computation_placer.h"
#include "tensorflow/compiler/xla/service/device_memory_allocator.h"
-#include "tensorflow/compiler/xla/service/pool.h"
+#include "tensorflow/compiler/xla/service/stream_pool.h"
#include "tensorflow/compiler/xla/service/transfer_manager.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/compiler/xla/types.h"
@@ -63,11 +63,9 @@ class BackendOptions {
//
// It also offers a pooling API for creation/use of initialized streams:
//
-// StreamPtr stream = backend->BorrowStream().ConsumeValueOrDie();
+// StreamPool::Ptr stream = backend->BorrowStream().ConsumeValueOrDie();
class Backend {
public:
- using StreamPtr = Pool<se::Stream>::SmartPtr;
-
// Creates a new backend.
static StatusOr<std::unique_ptr<Backend>> CreateBackend(
const BackendOptions& options);
@@ -114,13 +112,13 @@ class Backend {
// Borrows a stream for use by the caller, either by grabbing it from an
// internal pool, or by constructing/initializating it, and returns the result
// to the caller.
- StatusOr<StreamPtr> BorrowStream(int device_ordinal);
- StatusOr<StreamPtr> BorrowStream(se::StreamExecutor* executor);
+ StatusOr<StreamPool::Ptr> BorrowStream(int device_ordinal);
+ StatusOr<StreamPool::Ptr> BorrowStream(se::StreamExecutor* executor);
// Returns a function to borrow a stream, as `BorrowStream` above does.
// Purely for convenience, the caller could rather make this anonymous
// function itself.
- std::function<StatusOr<StreamPtr>(int)> StreamBorrower() {
+ std::function<StatusOr<StreamPool::Ptr>(int)> StreamBorrower() {
return [this](int device_ordinal) { return BorrowStream(device_ordinal); };
}
@@ -169,7 +167,7 @@ class Backend {
tensorflow::mutex mu_;
// Mapping from stream executor to stream pools, used by `BorrowStream` above.
- std::map<se::StreamExecutor*, Pool<se::Stream>> stream_pools_ GUARDED_BY(mu_);
+ std::map<se::StreamExecutor*, StreamPool> stream_pools_ GUARDED_BY(mu_);
// The default memory allocator to use.
std::unique_ptr<StreamExecutorMemoryAllocator> memory_allocator_;
diff --git a/tensorflow/compiler/xla/service/bfloat16_conversion_folding_test.cc b/tensorflow/compiler/xla/service/bfloat16_conversion_folding_test.cc
index f7b4c1405d..7cf05ca443 100644
--- a/tensorflow/compiler/xla/service/bfloat16_conversion_folding_test.cc
+++ b/tensorflow/compiler/xla/service/bfloat16_conversion_folding_test.cc
@@ -235,7 +235,8 @@ TEST_F(BFloat16ConversionFoldingTest, FoldCrossReplicaSumTupleOutput) {
HloInstruction* crs =
builder.AddInstruction(HloInstruction::CreateCrossReplicaSum(
ShapeUtil::MakeTupleShape({f32_shape, f32_shape}), {convert_a, b},
- sum, /*replica_group_ids=*/{}, /*barrier=*/""));
+ sum, /*replica_group_ids=*/{}, /*barrier=*/"",
+ /*all_reduce_id=*/tensorflow::gtl::nullopt));
HloInstruction* gte_a = builder.AddInstruction(
HloInstruction::CreateGetTupleElement(f32_shape, crs, 0));
HloInstruction* gte_b = builder.AddInstruction(
diff --git a/tensorflow/compiler/xla/service/bfloat16_normalization.cc b/tensorflow/compiler/xla/service/bfloat16_normalization.cc
index 14c54ddd13..16e99b5722 100644
--- a/tensorflow/compiler/xla/service/bfloat16_normalization.cc
+++ b/tensorflow/compiler/xla/service/bfloat16_normalization.cc
@@ -34,8 +34,10 @@ class BFloat16NormalizationVisitor : public DfsHloVisitorWithDefault {
Status DefaultAction(HloInstruction* hlo) override;
- // Special handling for cross-replica-sum which can have a tuple output.
+ // Special handling for cross-replica-sum and sort which can have a tuple
+ // output.
Status HandleCrossReplicaSum(HloInstruction* crs) override;
+ Status HandleSort(HloInstruction* sort) override;
static bool Run(HloComputation* computation,
const BFloat16Support* bfloat16_support) {
@@ -49,6 +51,10 @@ class BFloat16NormalizationVisitor : public DfsHloVisitorWithDefault {
// conversions between F32 and BF16 to make it supported.
Status HandleInstruction(HloInstruction* hlo);
+ // Handle instructions with tuple outputs by examining each output
+ // independently.
+ Status HandleMultipleOutputs(HloInstruction* hlo);
+
// Inserts a conversion HLO that changes the given HLO's output type.
Status InsertConvertAfterOutput(HloInstruction* hlo, PrimitiveType to,
HloComputation* computation);
@@ -148,22 +154,35 @@ Status BFloat16NormalizationVisitor::HandleCrossReplicaSum(
HloInstruction* crs) {
if (!ShapeUtil::IsTuple(crs->shape())) {
return HandleInstruction(crs);
+ } else {
+ return HandleMultipleOutputs(crs);
}
+}
+
+Status BFloat16NormalizationVisitor::HandleSort(HloInstruction* sort) {
+ if (!ShapeUtil::IsTuple(sort->shape())) {
+ return HandleInstruction(sort);
+ } else {
+ return HandleMultipleOutputs(sort);
+ }
+}
- std::vector<PrimitiveType> operand_types(crs->operand_count());
- std::vector<PrimitiveType> output_types(crs->operand_count());
+Status BFloat16NormalizationVisitor::HandleMultipleOutputs(
+ HloInstruction* hlo) {
+ std::vector<PrimitiveType> operand_types(hlo->operand_count());
+ std::vector<PrimitiveType> output_types(hlo->operand_count());
int64 f32_count = 0;
int64 bf16_count = 0;
bool has_unsupported_bf16_operand = false;
bool has_unsupported_bf16_output = false;
- for (int64 i = 0; i < crs->operand_count(); ++i) {
- operand_types[i] = crs->operand(i)->shape().element_type();
- output_types[i] = ShapeUtil::GetSubshape(crs->shape(), {i}).element_type();
+ for (int64 i = 0; i < hlo->operand_count(); ++i) {
+ operand_types[i] = hlo->operand(i)->shape().element_type();
+ output_types[i] = ShapeUtil::GetSubshape(hlo->shape(), {i}).element_type();
if (operand_types[i] == F32) {
f32_count += 1;
} else if (operand_types[i] == BF16) {
bf16_count += 1;
- if (!bfloat16_support_->SupportsBF16Operand(*crs, i)) {
+ if (!bfloat16_support_->SupportsBF16Operand(*hlo, i)) {
has_unsupported_bf16_operand = true;
}
}
@@ -171,7 +190,7 @@ Status BFloat16NormalizationVisitor::HandleCrossReplicaSum(
f32_count += 1;
} else if (output_types[i] == BF16) {
bf16_count += 1;
- if (!bfloat16_support_->SupportsBF16Output(*crs)) {
+ if (!bfloat16_support_->SupportsBF16Output(*hlo)) {
has_unsupported_bf16_output = true;
}
}
@@ -185,43 +204,43 @@ Status BFloat16NormalizationVisitor::HandleCrossReplicaSum(
if (operand_types[i] != BF16) {
return false;
}
- if (!bfloat16_support_->SupportsBF16Operand(*crs, i)) {
+ if (!bfloat16_support_->SupportsBF16Operand(*hlo, i)) {
return true;
}
- if (bfloat16_support_->SupportsMixedPrecisions(*crs)) {
+ if (bfloat16_support_->SupportsMixedPrecisions(*hlo)) {
return false;
}
return has_unsupported_bf16_operand || has_unsupported_bf16_output ||
f32_count > 0;
};
- for (int64 i = 0; i < crs->operand_count(); ++i) {
+ for (int64 i = 0; i < hlo->operand_count(); ++i) {
if (should_convert_operand(i)) {
- TF_RETURN_IF_ERROR(InsertConvertBeforeOperand(crs, i, F32, computation_));
+ TF_RETURN_IF_ERROR(InsertConvertBeforeOperand(hlo, i, F32, computation_));
f32_count += 1;
bf16_count -= 1;
}
}
if (!has_unsupported_bf16_output &&
- (bfloat16_support_->SupportsMixedPrecisions(*crs) || f32_count == 0 ||
+ (bfloat16_support_->SupportsMixedPrecisions(*hlo) || f32_count == 0 ||
bf16_count == 0)) {
return Status::OK();
}
- std::vector<HloInstruction*> materialized_users = crs->users();
- std::vector<HloInstruction*> output_elements(crs->operand_count());
- auto original_shape = crs->shape();
- for (int64 i = 0; i < crs->operand_count(); ++i) {
- auto subshape = ShapeUtil::GetMutableSubshape(crs->mutable_shape(), {i});
+ std::vector<HloInstruction*> materialized_users = hlo->users();
+ std::vector<HloInstruction*> output_elements(hlo->operand_count());
+ auto original_shape = hlo->shape();
+ for (int64 i = 0; i < hlo->operand_count(); ++i) {
+ auto subshape = ShapeUtil::GetMutableSubshape(hlo->mutable_shape(), {i});
if (output_types[i] != BF16) {
output_elements[i] = computation_->AddInstruction(
- HloInstruction::CreateGetTupleElement(*subshape, crs, i));
+ HloInstruction::CreateGetTupleElement(*subshape, hlo, i));
continue;
}
subshape->set_element_type(F32);
auto gte = computation_->AddInstruction(
- HloInstruction::CreateGetTupleElement(*subshape, crs, i));
+ HloInstruction::CreateGetTupleElement(*subshape, hlo, i));
output_elements[i] =
computation_->AddInstruction(HloInstruction::CreateConvert(
ShapeUtil::ChangeElementType(*subshape, BF16), gte));
@@ -229,11 +248,11 @@ Status BFloat16NormalizationVisitor::HandleCrossReplicaSum(
auto tuple = computation_->AddInstruction(
HloInstruction::CreateTuple(output_elements));
- // Use the crs' shape temporarily, in order to pass checks in
+ // Use the hlo' shape temporarily, in order to pass checks in
// ReplaceUseWith.
- *tuple->mutable_shape() = crs->shape();
+ *tuple->mutable_shape() = hlo->shape();
for (auto* user : materialized_users) {
- TF_RETURN_IF_ERROR(crs->ReplaceUseWith(user, tuple));
+ TF_RETURN_IF_ERROR(hlo->ReplaceUseWith(user, tuple));
}
*tuple->mutable_shape() = original_shape;
return Status::OK();
diff --git a/tensorflow/compiler/xla/service/bfloat16_normalization_test.cc b/tensorflow/compiler/xla/service/bfloat16_normalization_test.cc
index 830f26422b..f9f1f64998 100644
--- a/tensorflow/compiler/xla/service/bfloat16_normalization_test.cc
+++ b/tensorflow/compiler/xla/service/bfloat16_normalization_test.cc
@@ -251,7 +251,8 @@ TEST_F(BFloat16NormalizationTest, ResolveMixedPrecisionTupleCrossReplicaSum) {
HloInstruction* crs =
builder.AddInstruction(HloInstruction::CreateCrossReplicaSum(
ShapeUtil::MakeTupleShape({f32_shape, bf16_shape}), {a, b}, reduction,
- /*replica_group_ids=*/{}, /*barrier=*/""));
+ /*replica_group_ids=*/{}, /*barrier=*/"",
+ /*all_reduce_id=*/tensorflow::gtl::nullopt));
HloInstruction* gte = builder.AddInstruction(
HloInstruction::CreateGetTupleElement(bf16_shape, crs, 1));
@@ -265,6 +266,33 @@ TEST_F(BFloat16NormalizationTest, ResolveMixedPrecisionTupleCrossReplicaSum) {
EXPECT_EQ(ShapeUtil::GetSubshape(crs->shape(), {1}).element_type(), F32);
}
+TEST_F(BFloat16NormalizationTest, ResolveMixedPrecisionTupleSort) {
+ auto module = CreateNewModule();
+ auto builder = HloComputation::Builder(TestName());
+ Shape f32_shape = ShapeUtil::MakeShape(F32, {1024});
+ Shape bf16_shape = ShapeUtil::MakeShape(BF16, {1024});
+ Shape s32_shape = ShapeUtil::MakeShape(BF16, {1024});
+
+ HloInstruction* key = builder.AddInstruction(
+ HloInstruction::CreateParameter(0, f32_shape, "key"));
+ HloInstruction* value = builder.AddInstruction(
+ HloInstruction::CreateParameter(1, s32_shape, "value"));
+
+ HloInstruction* sort = builder.AddInstruction(HloInstruction::CreateSort(
+ ShapeUtil::MakeTupleShape({bf16_shape, s32_shape}), 0, key, value));
+ HloInstruction* gte = builder.AddInstruction(
+ HloInstruction::CreateGetTupleElement(bf16_shape, sort, 0));
+
+ auto computation = module->AddEntryComputation(builder.Build());
+
+ EXPECT_TRUE(Normalize(module.get()));
+
+ EXPECT_EQ(computation->root_instruction(), gte);
+ EXPECT_EQ(gte->shape().element_type(), BF16);
+ EXPECT_EQ(sort->operand(0)->shape().element_type(), F32);
+ EXPECT_EQ(ShapeUtil::GetSubshape(sort->shape(), {0}).element_type(), F32);
+}
+
// Tests that the normalization should not cause unsupported mixed precision due
// to resolving unsupported BF16 operand.
TEST_F(BFloat16NormalizationTest, DoNotAddUnsupportedMixedPrecision) {
diff --git a/tensorflow/compiler/xla/service/bfloat16_propagation.cc b/tensorflow/compiler/xla/service/bfloat16_propagation.cc
index b21c83a07f..2fb401c428 100644
--- a/tensorflow/compiler/xla/service/bfloat16_propagation.cc
+++ b/tensorflow/compiler/xla/service/bfloat16_propagation.cc
@@ -215,7 +215,12 @@ bool BFloat16Propagation::AllUsersConsumeBF16(const HloInstruction& hlo,
if (ContainsKey(values_that_must_be_kept_as_f32_, value)) {
return false;
}
- if (ValueTypeAfterChange(value) == BF16) {
+ // We use the original type for the value because we are going to examine
+ // the uses of it, instead of the value itself. If ValueTypeAfterChange()
+ // were used, it would cause problems when there are aliasing buffers, i.e.,
+ // ResolveInconsistencyOfAliasingBuffers() would fail to revert the
+ // tentative change to BF16 even if the uses require F32.
+ if (value->shape().element_type() == BF16) {
continue;
}
for (const HloUse& use : value->uses()) {
@@ -566,6 +571,9 @@ bool BFloat16Propagation::ResolveInconsistencyOfAliasingBuffersHelper(
}
visited_computations->insert(visited_in_while.begin(),
visited_in_while.end());
+ } else if (hlo->opcode() == HloOpcode::kFusion) {
+ ResolveInconsistencyOfAliasingBuffersHelper(
+ hlo->fused_instructions_computation(), visited_computations);
}
}
// Now adjust parameters of called computations.
@@ -769,8 +777,7 @@ StatusOr<bool> BFloat16Propagation::Run(HloModule* module) {
// propagation in reverse topological order.
for (auto comp_it = computations_topological_order.rbegin();
comp_it != computations_topological_order.rend(); ++comp_it) {
- if ((*comp_it)->IsFusionComputation()) {
- // Fusion computations are handled when visiting the fusion instruction.
+ if (ContainsKey(computations_visited_in_backward_pass_, *comp_it)) {
continue;
}
auto insts = (*comp_it)->MakeInstructionPostOrder();
@@ -778,6 +785,7 @@ StatusOr<bool> BFloat16Propagation::Run(HloModule* module) {
DetermineInstructionPrecision(*inst_it,
/*skip_parameters=*/true);
}
+ computations_visited_in_backward_pass_.insert(*comp_it);
}
// It's possible that an instruction does not define a buffer, but the
diff --git a/tensorflow/compiler/xla/service/bfloat16_propagation_test.cc b/tensorflow/compiler/xla/service/bfloat16_propagation_test.cc
index aeafb25ad7..69b654d30e 100644
--- a/tensorflow/compiler/xla/service/bfloat16_propagation_test.cc
+++ b/tensorflow/compiler/xla/service/bfloat16_propagation_test.cc
@@ -508,6 +508,63 @@ TEST_F(BFloat16PropagationTest, PropagateThroughSimpleWhile) {
EXPECT_FALSE(OutputsBF16(dot));
}
+// Tests that if the while condition prevents using BF16, no changes should be
+// made to the while body and thus the fusion node inside it.
+TEST_F(BFloat16PropagationTest,
+ ConditionPreventsPropagationForFusionInsideWhile) {
+ auto module = CreateNewModule();
+ auto builder = HloComputation::Builder(TestName());
+ Shape shape = ShapeUtil::MakeShape(F32, {4, 4});
+
+ HloInstruction* param0 = builder.AddInstruction(
+ HloInstruction::CreateParameter(0, shape, "param0"));
+ HloInstruction* param1 = builder.AddInstruction(
+ HloInstruction::CreateParameter(1, shape, "param1"));
+ HloInstruction* add = builder.AddInstruction(
+ HloInstruction::CreateBinary(shape, HloOpcode::kAdd, param0, param1));
+
+ auto builder_cond = HloComputation::Builder("cond");
+ auto cond_param = builder_cond.AddInstruction(
+ HloInstruction::CreateParameter(0, shape, "cond_param"));
+ builder_cond.AddInstruction(HloInstruction::CreateBinary(
+ ShapeUtil::MakeShape(PRED, {}), HloOpcode::kGt,
+ builder_cond.AddInstruction(HloInstruction::CreateSlice(
+ ShapeUtil::MakeShape(F32, {}), cond_param, {0, 0}, {1, 1}, {1, 1})),
+ builder_cond.AddInstruction(HloInstruction::CreateSlice(
+ ShapeUtil::MakeShape(F32, {}), cond_param, {1, 1}, {2, 2}, {1, 1}))));
+ auto cond = module->AddEmbeddedComputation(builder_cond.Build());
+
+ auto builder_body = HloComputation::Builder("body");
+ auto body_param = builder_body.AddInstruction(
+ HloInstruction::CreateParameter(0, shape, "body_param"));
+ auto body_transpose = builder_body.AddInstruction(
+ HloInstruction::CreateTranspose(shape, body_param, {0, 1}));
+
+ auto builder_f = HloComputation::Builder("fusion");
+ HloInstruction* a_f =
+ builder_f.AddInstruction(HloInstruction::CreateParameter(0, shape, "a"));
+ builder_f.AddInstruction(HloInstruction::CreateTranspose(shape, a_f, {0, 1}));
+ auto comp_f = module->AddEmbeddedComputation(builder_f.Build());
+ auto body_fusion = builder_body.AddInstruction(HloInstruction::CreateFusion(
+ shape, HloInstruction::FusionKind::kCustom, {body_transpose}, comp_f));
+ auto body = module->AddEmbeddedComputation(builder_body.Build());
+
+ auto while_hlo = builder.AddInstruction(
+ HloInstruction::CreateWhile(shape, cond, body, add));
+
+ auto dot = builder.AddInstruction(HloInstruction::CreateBinary(
+ shape, HloOpcode::kDot, while_hlo, while_hlo));
+ auto computation = module->AddEntryComputation(builder.Build());
+
+ EXPECT_FALSE(PropagatePrecision(module.get()));
+ EXPECT_EQ(computation->root_instruction(), dot);
+ EXPECT_FALSE(OutputsBF16(add));
+ EXPECT_FALSE(OutputsBF16(body_fusion));
+ EXPECT_FALSE(OutputsBF16(body_param));
+ EXPECT_FALSE(OutputsBF16(body_transpose));
+ EXPECT_FALSE(OutputsBF16(a_f));
+}
+
// Tests that BF16 is propagated properly through while computations with
// tuple-shaped input/output.
TEST_F(BFloat16PropagationTest, PropagateThroughTupleWhile) {
@@ -553,10 +610,14 @@ TEST_F(BFloat16PropagationTest, PropagateThroughTupleWhile) {
HloInstruction::CreateGetTupleElement(shape, body_param, 0));
auto body_rhs = builder_body.AddInstruction(
HloInstruction::CreateGetTupleElement(shape, body_param, 1));
- auto body_dot = builder_body.AddInstruction(
+ auto body_dot1 = builder_body.AddInstruction(
HloInstruction::CreateBinary(shape, HloOpcode::kDot, body_lhs, body_rhs));
+ auto body_dot2 = builder_body.AddInstruction(
+ HloInstruction::CreateBinary(shape, HloOpcode::kDot, body_rhs, body_lhs));
+ auto body_transpose = builder_body.AddInstruction(
+ HloInstruction::CreateTranspose(shape, body_dot2, {0, 1}));
builder_body.AddInstruction(
- HloInstruction::CreateTuple({body_dot, body_rhs}));
+ HloInstruction::CreateTuple({body_dot1, body_transpose}));
auto body = module->AddEmbeddedComputation(builder_body.Build());
auto while_hlo = builder.AddInstruction(
@@ -575,9 +636,11 @@ TEST_F(BFloat16PropagationTest, PropagateThroughTupleWhile) {
EXPECT_EQ(computation->root_instruction(), dot);
EXPECT_TRUE(OutputsBF16(lhs));
EXPECT_FALSE(OutputsBF16(rhs));
- EXPECT_TRUE(OutputsBF16(body_dot));
+ EXPECT_TRUE(OutputsBF16(body_dot1));
EXPECT_TRUE(OutputsBF16(body_lhs));
EXPECT_FALSE(OutputsBF16(body_rhs));
+ EXPECT_FALSE(OutputsBF16(body_dot2));
+ EXPECT_FALSE(OutputsBF16(body_transpose));
EXPECT_TRUE(OutputsBF16(cond_lhs));
EXPECT_FALSE(OutputsBF16(cond_rhs));
EXPECT_TRUE(OutputsBF16(add0));
diff --git a/tensorflow/compiler/xla/service/buffer_assignment.cc b/tensorflow/compiler/xla/service/buffer_assignment.cc
index b4c7cf0dd8..e4d2e73b99 100644
--- a/tensorflow/compiler/xla/service/buffer_assignment.cc
+++ b/tensorflow/compiler/xla/service/buffer_assignment.cc
@@ -817,8 +817,7 @@ bool BufferAssigner::MaybeAssignBuffer(BufferAllocation* allocation,
}
Status BufferAssigner::AssignBuffersForComputation(
- const HloComputation* computation, const DebugOptions& debug_options,
- bool is_thread_local,
+ const HloComputation* computation, bool is_thread_local,
const FlatSet<const LogicalBuffer*>& colocated_buffers,
const FlatSet<BufferAllocation::Index>& colocated_allocations,
FlatMap<const HloComputation*, FlatSet<const LogicalBuffer*>>*
@@ -1342,11 +1341,25 @@ BufferAssigner::MergeColocatedBufferSets(
auto cannot_merge_buffer_sets = [&colocated_buffer_sets, &buffer_liveness,
&buffer_size,
&is_entry_parameter](int64 i, int64 j) {
- // Do not merge if one of the sets includes live outs or entry parameters.
+ // Do not merge if one of the sets includes live outs, entry parameters or
+ // constants.
+ //
+ // Buffer liveness does not report the correct live range for entry
+ // parameter and live out buffers so we have to special case them here. On
+ // backends that support constant buffer allocations, constant buffers are
+ // assigned globals in readonly storage so we can't merge colocated buffer
+ // sets containing constants with colocated buffer sets containing writing
+ // instructions or other constants.
+ //
+ // Moreover (on the CPU/GPU backends) the entry parameter buffers belong to
+ // the caller of the executable so we can't write to entry parameters
+ // either, and the argument for not merging constants also applies to entry
+ // parameters.
for (int64 key : {i, j}) {
for (auto& buffer : colocated_buffer_sets[key]) {
if (buffer_liveness.MaybeLiveOut(*buffer) ||
- is_entry_parameter(*buffer)) {
+ is_entry_parameter(*buffer) ||
+ buffer->instruction()->opcode() == HloOpcode::kConstant) {
return true;
}
}
@@ -1664,7 +1677,7 @@ StatusOr<std::unique_ptr<BufferAssignment>> BufferAssigner::CreateAssignment(
buffers_to_assign_sequentially;
for (auto* computation : global_computations) {
TF_RETURN_IF_ERROR(AssignBuffersForComputation(
- computation, module->config().debug_options(),
+ computation,
/*is_thread_local=*/false, colocated_buffers, colocated_allocations,
&buffers_to_assign_sequentially, assignment.get()));
}
@@ -1685,7 +1698,7 @@ StatusOr<std::unique_ptr<BufferAssignment>> BufferAssigner::CreateAssignment(
continue;
}
TF_RETURN_IF_ERROR(AssignBuffersForComputation(
- computation, module->config().debug_options(),
+ computation,
/*is_thread_local=*/true, colocated_buffers, colocated_allocations,
/*buffers_to_assign_sequentially=*/nullptr, assignment.get()));
}
diff --git a/tensorflow/compiler/xla/service/buffer_assignment.h b/tensorflow/compiler/xla/service/buffer_assignment.h
index 4fcf1fc73d..94495290c1 100644
--- a/tensorflow/compiler/xla/service/buffer_assignment.h
+++ b/tensorflow/compiler/xla/service/buffer_assignment.h
@@ -32,7 +32,6 @@ limitations under the License.
#include "tensorflow/compiler/xla/service/tuple_points_to_analysis.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/compiler/xla/types.h"
-#include "tensorflow/compiler/xla/xla_data.pb.h"
#include "tensorflow/core/lib/gtl/array_slice.h"
#include "tensorflow/core/lib/gtl/flatmap.h"
#include "tensorflow/core/lib/gtl/flatset.h"
@@ -543,8 +542,7 @@ class BufferAssigner {
// true, then all assigned buffers have the is_thread_local flag set to
// true.
Status AssignBuffersForComputation(
- const HloComputation* computation, const DebugOptions& debug_options,
- bool is_thread_local,
+ const HloComputation* computation, bool is_thread_local,
const tensorflow::gtl::FlatSet<const LogicalBuffer*>& colocated_buffers,
const tensorflow::gtl::FlatSet<BufferAllocation::Index>&
colocated_allocations,
diff --git a/tensorflow/compiler/xla/service/buffer_assignment_test.cc b/tensorflow/compiler/xla/service/buffer_assignment_test.cc
index dea855d39a..eccb146a0d 100644
--- a/tensorflow/compiler/xla/service/buffer_assignment_test.cc
+++ b/tensorflow/compiler/xla/service/buffer_assignment_test.cc
@@ -1923,6 +1923,74 @@ ENTRY %test_module {
EXPECT_NE(slice_param, slice_while1);
}
+TEST_F(WhileBufferAssignmentTest, ColocatedBufferWithConstant) {
+ const Shape r0s32 = ShapeUtil::MakeShape(S32, {});
+
+ const char* module_str = R"(
+HloModule test_module
+
+%cond.v0 {
+ %param = s32[] parameter(0)
+ ROOT %constant = pred[] constant(true)
+}
+
+%cond.v1 {
+ %param.0 = s32[] parameter(0)
+ ROOT %constant.0 = pred[] constant(true)
+}
+
+%body.v0 {
+ ROOT %param.1 = s32[] parameter(0)
+}
+
+%body.v1 {
+ %param.2 = s32[] parameter(0)
+ ROOT add = s32[] add(%param.2, %param.2)
+}
+
+ENTRY %test_module {
+ %constant.42 = s32[] constant(42)
+ %while.0 = s32[] while(%constant.42), condition=%cond.v0, body=%body.v0
+ %mul = s32[] multiply(%while.0, %while.0)
+ %while.1 = s32[] while(%mul), condition=%cond.v1, body=%body.v1
+ ROOT %bcast = s32[1024,1024]{1,0} broadcast(s32[] %while.1), dimensions={}
+})";
+
+ TF_ASSERT_OK_AND_ASSIGN(std::unique_ptr<HloModule> module,
+ ParseHloString(module_str));
+
+ // Run CopyInsertion and check if the graph constructed above doesn't need
+ // any copies inserted for BufferAssignment to run.
+ int64 instruction_count = module->instruction_count();
+ CopyInsertion copy_insertion;
+ ASSERT_IS_OK(copy_insertion.Run(module.get()).status());
+ ASSERT_EQ(instruction_count, module->instruction_count());
+
+ // Get the instructions in the module.
+ const HloInstruction* bcast = module->entry_computation()->root_instruction();
+ const HloInstruction* constant =
+ module->entry_computation()->GetInstructionWithName("constant.42");
+ ASSERT_EQ(bcast->opcode(), HloOpcode::kBroadcast);
+ const HloInstruction* while1 = bcast->operand(0);
+ ASSERT_EQ(while1->opcode(), HloOpcode::kWhile);
+ const HloInstruction* while0 = while1->operand(0)->operand(0);
+ ASSERT_EQ(while0->opcode(), HloOpcode::kWhile);
+
+ // Run buffer assignment.
+ auto assignment = RunBufferAssignment(module.get());
+ TF_ASSERT_OK_AND_ASSIGN(auto slice_constant,
+ assignment->GetUniqueSlice(constant, {}));
+ TF_ASSERT_OK_AND_ASSIGN(auto slice_while0,
+ assignment->GetUniqueSlice(while0, {}));
+ TF_ASSERT_OK_AND_ASSIGN(auto slice_while1,
+ assignment->GetUniqueSlice(while1, {}));
+
+ // The constant slice is part of the while0's colocation set (init value), but
+ // not merged into the while1's colocation set.
+ EXPECT_EQ(slice_constant, slice_while0);
+ EXPECT_NE(slice_constant, slice_while1);
+}
+
// Tests that the colocated buffers for while instructions are properly assigned
// during buffer assignment such that the result tuple elements are not assigned
// to the same buffer.
diff --git a/tensorflow/compiler/xla/service/computation_placer.cc b/tensorflow/compiler/xla/service/computation_placer.cc
index d26486fcfe..187ce568cb 100644
--- a/tensorflow/compiler/xla/service/computation_placer.cc
+++ b/tensorflow/compiler/xla/service/computation_placer.cc
@@ -29,9 +29,13 @@ limitations under the License.
#include "tensorflow/compiler/xla/util.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/core/status.h"
+#include "tensorflow/core/lib/strings/strcat.h"
#include "tensorflow/core/platform/logging.h"
#include "tensorflow/core/platform/stream_executor_no_cuda.h"
+using tensorflow::strings::StrAppend;
+using tensorflow::strings::StrCat;
+
namespace xla {
Status DeviceAssignment::Serialize(DeviceAssignmentProto* proto) const {
@@ -71,6 +75,19 @@ DeviceAssignment::Deserialize(const DeviceAssignmentProto& proto) {
return std::move(assignment);
}
+string DeviceAssignment::ToString() const {
+ string output = StrCat("Computations: ", computation_count(),
+ " Replicas: ", replica_count(), "\n");
+ for (int computation = 0; computation < computation_count(); ++computation) {
+ StrAppend(&output, "Computation ", computation, ": ");
+ for (int replica = 0; replica < replica_count(); ++replica) {
+ StrAppend(&output, operator()(replica, computation), " ");
+ }
+ StrAppend(&output, "\n");
+ }
+ return output;
+}
+
StatusOr<int> ComputationPlacer::DeviceId(int replica, int computation,
int replica_count,
int computation_count) {
diff --git a/tensorflow/compiler/xla/service/computation_placer.h b/tensorflow/compiler/xla/service/computation_placer.h
index 737d00e93e..c899ffb9dc 100644
--- a/tensorflow/compiler/xla/service/computation_placer.h
+++ b/tensorflow/compiler/xla/service/computation_placer.h
@@ -55,6 +55,8 @@ class DeviceAssignment : public Array2D<int> {
// due to a StatusOr of an incomplete type (DeviceAssignment).
static StatusOr<std::unique_ptr<DeviceAssignment>> Deserialize(
const DeviceAssignmentProto& proto);
+
+ string ToString() const;
};
// A generic implementation of the XLA computation placer, which assigns device
diff --git a/tensorflow/compiler/xla/service/cpu/BUILD b/tensorflow/compiler/xla/service/cpu/BUILD
index ace9f96cfb..504b61d134 100644
--- a/tensorflow/compiler/xla/service/cpu/BUILD
+++ b/tensorflow/compiler/xla/service/cpu/BUILD
@@ -252,6 +252,7 @@ cc_library(
"//tensorflow/compiler/xla/service:hlo_module_config",
"//tensorflow/compiler/xla/service:name_uniquer",
"//tensorflow/compiler/xla/service/llvm_ir:alias_analysis",
+ "//tensorflow/compiler/xla/service/llvm_ir:buffer_assignment_util",
"//tensorflow/compiler/xla/service/llvm_ir:dynamic_update_slice_util",
"//tensorflow/compiler/xla/service/llvm_ir:fused_ir_emitter",
"//tensorflow/compiler/xla/service/llvm_ir:ir_array",
@@ -363,8 +364,8 @@ tf_cc_binary(
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/core:lib",
],
)
@@ -444,6 +445,7 @@ cc_library(
deps = [
":vector_support_library",
"//tensorflow/compiler/xla/service/llvm_ir:llvm_util",
+ "//tensorflow/compiler/xla/service/llvm_ir:math_ops",
"//tensorflow/core:lib",
"@llvm//:core",
"@llvm//:transform_utils",
diff --git a/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc b/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc
index 29fa29d33a..b49ea89896 100644
--- a/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc
+++ b/tensorflow/compiler/xla/service/cpu/cpu_compiler.cc
@@ -562,7 +562,9 @@ StatusOr<std::unique_ptr<Executable>> CpuCompiler::RunBackend(
BufferAssigner::Run(
module.get(),
xla::MakeUnique<SequentialHloOrdering>(module.get(), module_sequence),
- BufferSizeBytesFunction(), memory_alignment));
+ BufferSizeBytesFunction(), memory_alignment,
+ /*allow_input_output_aliasing=*/false,
+ /*allocate_buffers_for_constants=*/true));
// BufferAssignment::ToString() includes a header, so no need for us to
// print one ourselves.
XLA_VLOG_LINES(2, assignment->ToString());
@@ -584,6 +586,8 @@ StatusOr<std::unique_ptr<Executable>> CpuCompiler::RunBackend(
std::move(computation_to_profile_idx),
&target_machine_features);
+ TF_RETURN_IF_ERROR(ir_emitter.EmitConstantGlobals());
+
for (auto embedded_computation :
entry_computation->MakeEmbeddedComputationsList()) {
if (embedded_computation->IsFusionComputation()) {
@@ -747,7 +751,9 @@ CpuCompiler::CompileAheadOfTime(std::vector<std::unique_ptr<HloModule>> modules,
BufferAssigner::Run(
module,
xla::MakeUnique<SequentialHloOrdering>(module, module_sequence),
- BufferSizeBytesFunction(), memory_alignment));
+ BufferSizeBytesFunction(), memory_alignment,
+ /*allow_input_output_aliasing=*/false,
+ /*allocate_buffers_for_constants=*/true));
// BufferAssignment::ToString() includes a header, so no need for us to
// print one ourselves.
XLA_VLOG_LINES(2, assignment->ToString());
@@ -776,6 +782,9 @@ CpuCompiler::CompileAheadOfTime(std::vector<std::unique_ptr<HloModule>> modules,
std::move(instruction_to_profile_idx),
std::move(computation_to_profile_idx),
&target_machine_features);
+
+ TF_RETURN_IF_ERROR(ir_emitter.EmitConstantGlobals());
+
HloComputation* computation = module->entry_computation();
for (auto embedded_computation :
computation->MakeEmbeddedComputationsList()) {
@@ -832,7 +841,8 @@ CpuCompiler::CompileAheadOfTime(std::vector<std::unique_ptr<HloModule>> modules,
BufferSizes buffer_sizes;
for (const BufferAllocation& allocation : assignment->Allocations()) {
// Callers don't need to allocate temporary buffers for parameters.
- if (allocation.is_entry_computation_parameter()) {
+ if (allocation.is_entry_computation_parameter() ||
+ allocation.is_constant()) {
buffer_sizes.push_back(-1);
continue;
}
diff --git a/tensorflow/compiler/xla/service/cpu/cpu_executable.cc b/tensorflow/compiler/xla/service/cpu/cpu_executable.cc
index 1093559892..81e17a5cd4 100644
--- a/tensorflow/compiler/xla/service/cpu/cpu_executable.cc
+++ b/tensorflow/compiler/xla/service/cpu/cpu_executable.cc
@@ -88,6 +88,11 @@ Status CpuExecutable::AllocateBuffers(
continue;
}
+ if (allocation.is_constant()) {
+ VLOG(3) << "allocation #" << i << " is a constant";
+ continue;
+ }
+
if (allocation.is_thread_local()) {
VLOG(3) << "buffer #" << i << " is thread-local";
continue;
diff --git a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc
index 1fdeceb860..645888de78 100644
--- a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc
+++ b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.cc
@@ -1278,10 +1278,10 @@ Status DotOpEmitter::Emit() {
// operand dimensions. The reduction dimension of the LHS and RHS are handled
// in a separate innermost loop which performs the sum of products.
llvm_ir::ForLoopNest loop_nest(llvm_ir::IrName(&dot_), b_);
- llvm_ir::IrArray::Index lhs_index = EmitOperandArrayLoopNest(
- &loop_nest, lhs_array_, lhs_reduction_dimension, "lhs");
- llvm_ir::IrArray::Index rhs_index = EmitOperandArrayLoopNest(
- &loop_nest, rhs_array_, rhs_reduction_dimension, "rhs");
+ llvm_ir::IrArray::Index lhs_index = loop_nest.EmitOperandArrayLoopNest(
+ lhs_array_, /*dimension_to_skip=*/lhs_reduction_dimension, "lhs");
+ llvm_ir::IrArray::Index rhs_index = loop_nest.EmitOperandArrayLoopNest(
+ rhs_array_, /*dimension_to_skip=*/rhs_reduction_dimension, "rhs");
// Create the loop which does the sum of products reduction.
//
@@ -1537,36 +1537,6 @@ DotOpEmitter::MatMultDims DotOpEmitter::GetMatMultDims() const {
LayoutUtil::Minor(target_array_.GetShape().layout(), 0) == 0};
}
-llvm_ir::IrArray::Index DotOpEmitter::EmitOperandArrayLoopNest(
- llvm_ir::ForLoopNest* loop_nest, const llvm_ir::IrArray& operand_array,
- int64 reduction_dimension, tensorflow::StringPiece name_suffix) {
- // Prepares the dimension list we will use to emit the loop nest. Outermost
- // loops are added first. Add loops in major-to-minor order, and skip the
- // reduction dimension.
- std::vector<int64> dimensions;
- const Shape& shape = operand_array.GetShape();
- for (int i = LayoutUtil::MinorToMajor(shape).size() - 1; i >= 0; --i) {
- int64 dimension = LayoutUtil::Minor(shape.layout(), i);
- if (dimension != reduction_dimension) {
- dimensions.push_back(dimension);
- }
- }
-
- // Create loop nest with one for-loop for each dimension of the
- // output.
- llvm_ir::IrArray::Index index =
- loop_nest->AddLoopsForShapeOnDimensions(shape, dimensions, name_suffix);
- // Verify every dimension except the reduction dimension was set in the index.
- for (int dimension = 0; dimension < index.size(); ++dimension) {
- if (dimension == reduction_dimension) {
- DCHECK_EQ(nullptr, index[dimension]);
- } else {
- DCHECK_NE(nullptr, index[dimension]);
- }
- }
- return index;
-}
-
// Return whether the given shape is a matrix with no padding.
static bool IsRank2WithNoPadding(const Shape& shape) {
return ShapeUtil::Rank(shape) == 2 && !LayoutUtil::IsPadded(shape);
diff --git a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.h b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.h
index c2eeb0a1f9..590032fbe9 100644
--- a/tensorflow/compiler/xla/service/cpu/dot_op_emitter.h
+++ b/tensorflow/compiler/xla/service/cpu/dot_op_emitter.h
@@ -88,17 +88,6 @@ class DotOpEmitter {
// Emits a call to the CPU runtime to perform the matrix multiply.
Status EmitCallToRuntime();
- // Emits a series of nested loops for iterating over an operand array in the
- // dot operation. Loops are constructed in major to minor dimension layout
- // order. No loop is emitted for the given reduction_dimension. The function
- // returns an IrArray index for the given operand_array containing the indvars
- // of the loops. All dimensions of the index are filled except for the
- // reduction dimension. name_suffix is the string to append to the names of
- // LLVM constructs (eg, basic blocks) constructed by this method.
- llvm_ir::IrArray::Index EmitOperandArrayLoopNest(
- llvm_ir::ForLoopNest* loop_nest, const llvm_ir::IrArray& operand_array,
- int64 reduction_dimension, tensorflow::StringPiece name_suffix);
-
// Represents the dimensions of a matrix-matrix multiply operation.
struct MatMultDims {
// The number of rows in the LHS.
diff --git a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc
index d4ac35a604..a6d8551841 100644
--- a/tensorflow/compiler/xla/service/cpu/ir_emitter.cc
+++ b/tensorflow/compiler/xla/service/cpu/ir_emitter.cc
@@ -51,6 +51,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/service/hlo_casting_utils.h"
#include "tensorflow/compiler/xla/service/hlo_instructions.h"
#include "tensorflow/compiler/xla/service/hlo_opcode.h"
+#include "tensorflow/compiler/xla/service/llvm_ir/buffer_assignment_util.h"
#include "tensorflow/compiler/xla/service/llvm_ir/dynamic_update_slice_util.h"
#include "tensorflow/compiler/xla/service/llvm_ir/fused_ir_emitter.h"
#include "tensorflow/compiler/xla/service/llvm_ir/llvm_loop.h"
@@ -175,25 +176,36 @@ llvm::Constant* IrEmitter::EmitGlobalForLiteral(const Literal& literal) {
result_global, IrShapeType(literal.shape())->getPointerTo());
}
-Status IrEmitter::HandleConstant(HloInstruction* constant) {
- VLOG(2) << "HandleConstant: " << constant->ToString();
- const Literal& literal = constant->literal();
- llvm::Constant* global_for_const;
+Status IrEmitter::EmitConstantGlobals() {
+ for (const BufferAllocation& allocation : assignment_.Allocations()) {
+ if (!allocation.is_constant()) {
+ continue;
+ }
- auto it = emitted_literals_.find(&literal);
- if (it != emitted_literals_.end()) {
- global_for_const = it->second;
- } else {
- global_for_const = EmitGlobalForLiteral(literal);
- emitted_literals_[&literal] = global_for_const;
+ const Literal& literal = llvm_ir::LiteralForConstantAllocation(allocation);
+ llvm::Constant* global_for_const;
+ auto it = emitted_literals_.find(&literal);
+ if (it != emitted_literals_.end()) {
+ global_for_const = it->second;
+ } else {
+ global_for_const = EmitGlobalForLiteral(literal);
+ InsertOrDie(&emitted_literals_, &literal, global_for_const);
+ }
+
+ InsertOrDie(&constant_buffer_to_global_, allocation.index(),
+ global_for_const);
}
- emitted_value_[constant] = global_for_const;
- VLOG(2) << " emitted value: " << llvm_ir::DumpToString(*global_for_const);
- VLOG(2) << " its type: "
- << llvm_ir::DumpToString(*global_for_const->getType());
+
return Status::OK();
}
+Status IrEmitter::HandleConstant(HloInstruction* constant) {
+ VLOG(2) << "HandleConstant: " << constant->ToString();
+ // IrEmitter::EmitConstantGlobals has already taken care of emitting the body
+ // of the constant.
+ return EmitTargetAddressForOp(constant);
+}
+
Status IrEmitter::HandleCopy(HloInstruction* copy) {
if (ShapeUtil::IsTuple(copy->shape())) {
// kCopy shallow copies a tuple so just memcpy the top-level buffer.
@@ -2506,6 +2518,23 @@ Status IrEmitter::HandleIota(HloInstruction* iota) {
return Unimplemented("Iota is not implemented on CPU.");
}
+Status IrEmitter::HandleRng(HloInstruction* rng) {
+ ElementalIrEmitter::HloToElementGeneratorMap operand_to_generator;
+ for (const HloInstruction* operand : rng->operands()) {
+ operand_to_generator[operand] = [=](const llvm_ir::IrArray::Index& index) {
+ return GetIrArrayFor(operand).EmitReadArrayElement(index, &b_);
+ };
+ }
+
+ CpuElementalIrEmitter elemental_emitter(hlo_module_config_, this, module_);
+ TF_RETURN_IF_ERROR(EmitTargetElementLoop(
+ rng, elemental_emitter.MakeElementGenerator(rng, operand_to_generator)));
+
+ llvm_ir::IncrementVariableForPhiloxRngState(1, module_, &b_);
+
+ return Status::OK();
+}
+
Status IrEmitter::FinishVisit(HloInstruction* root) {
// When this method is called, we should have already emitted an IR value for
// the root (return) op. The IR value holds the address of the buffer holding
@@ -2695,6 +2724,10 @@ llvm::Value* IrEmitter::EmitTempBufferPointer(
return b_.CreateBitCast(tempbuf_address, element_type->getPointerTo());
}
+ if (allocation.is_constant()) {
+ return FindOrDie(constant_buffer_to_global_, allocation.index());
+ }
+
llvm::Value* tempbuf_address_ptr = llvm_ir::EmitBufferIndexingGEP(
GetTempBuffersArgument(), slice.index(), &b_);
llvm::LoadInst* tempbuf_address_base = b_.CreateLoad(tempbuf_address_ptr);
diff --git a/tensorflow/compiler/xla/service/cpu/ir_emitter.h b/tensorflow/compiler/xla/service/cpu/ir_emitter.h
index 4e928ffadc..03bbb2afb5 100644
--- a/tensorflow/compiler/xla/service/cpu/ir_emitter.h
+++ b/tensorflow/compiler/xla/service/cpu/ir_emitter.h
@@ -105,6 +105,9 @@ class IrEmitter : public DfsHloVisitorWithDefault {
PrimitiveType return_type, HloComputation* computation,
const std::vector<llvm::Value*>& arguments, tensorflow::StringPiece name);
+ // Emit an LLVM global variable for every constant buffer allocation.
+ Status EmitConstantGlobals();
+
protected:
//
// The following methods implement the DfsHloVisitor interface.
@@ -149,6 +152,7 @@ class IrEmitter : public DfsHloVisitorWithDefault {
Status HandleConditional(HloInstruction* conditional) override;
Status HandleAfterAll(HloInstruction* gen_token) override;
Status HandleIota(HloInstruction* iota) override;
+ Status HandleRng(HloInstruction* rng) override;
Status FinishVisit(HloInstruction* root) override;
Status Preprocess(HloInstruction* hlo) override;
@@ -559,6 +563,9 @@ class IrEmitter : public DfsHloVisitorWithDefault {
LiteralPtrHashFunctor, LiteralPtrEqualityFunctor>
emitted_literals_;
+ tensorflow::gtl::FlatMap<BufferAllocation::Index, llvm::Constant*>
+ constant_buffer_to_global_;
+
TF_DISALLOW_COPY_AND_ASSIGN(IrEmitter);
};
diff --git a/tensorflow/compiler/xla/service/cpu/llvm_ir_runtime.cc b/tensorflow/compiler/xla/service/cpu/llvm_ir_runtime.cc
index ec0498e04e..cef5e57b0b 100644
--- a/tensorflow/compiler/xla/service/cpu/llvm_ir_runtime.cc
+++ b/tensorflow/compiler/xla/service/cpu/llvm_ir_runtime.cc
@@ -21,6 +21,7 @@ limitations under the License.
#include "llvm/IR/Verifier.h"
#include "llvm/Transforms/Utils/Cloning.h"
#include "tensorflow/compiler/xla/service/cpu/vector_support_library.h"
+#include "tensorflow/compiler/xla/service/llvm_ir/math_ops.h"
#include "tensorflow/core/lib/core/casts.h"
#include "tensorflow/core/platform/logging.h"
@@ -54,44 +55,12 @@ llvm::Function* EmitVectorF32TanhIfNeeded(llvm::Module* module,
llvm::IRBuilder<> b(vector_tanh_body);
llvm::FastMathFlags fast_math_flags;
- fast_math_flags.setFast();
+ fast_math_flags.setFast(enable_fast_math);
b.setFastMathFlags(fast_math_flags);
- VectorSupportLibrary vsl(F32, vector_width, &b, "tanh_f32");
-
llvm::Value* input = &*vector_tanh_function->arg_begin();
- CHECK_EQ(input->getType(), vsl.vector_type());
-
- // This implements the same rational interpolant as implemented in Eigen3.
- llvm::Value* input_clamped =
- vsl.Clamp(input, /*low=*/GetIeeeF32(-9.0), /*high=*/GetIeeeF32(9.0));
-
- std::array<float, 7> numerator_coeffs{
- -2.76076847742355e-16f, 2.00018790482477e-13f, -8.60467152213735e-11f,
- 5.12229709037114e-08f, 1.48572235717979e-05f, 6.37261928875436e-04f,
- 4.89352455891786e-03f};
-
- std::array<float, 4> denominator_coeffs{
- 1.19825839466702e-06f, 1.18534705686654e-04f, 2.26843463243900e-03f,
- 4.89352518554385e-03f};
-
- llvm::Value* input_squared = vsl.Mul(input_clamped, input_clamped);
- llvm::Value* numerator = vsl.SplatFloat(GetIeeeF32(numerator_coeffs[0]));
- for (int i = 1; i < numerator_coeffs.size(); i++) {
- numerator =
- vsl.MulAdd(input_squared, numerator, GetIeeeF32(numerator_coeffs[i]));
- }
-
- numerator = vsl.Mul(input_clamped, numerator);
-
- llvm::Value* denominator = vsl.SplatFloat(GetIeeeF32(denominator_coeffs[0]));
- for (int i = 1; i < denominator_coeffs.size(); i++) {
- denominator = vsl.MulAdd(input_squared, denominator,
- GetIeeeF32(denominator_coeffs[i]));
- }
-
- llvm::Value* result = vsl.Div(numerator, denominator);
- b.CreateRet(result);
+ CHECK_EQ(vector_width, input->getType()->getVectorNumElements());
+ b.CreateRet(llvm_ir::EmitFastTanh(&b, input));
DCHECK(!llvm::verifyFunction(*vector_tanh_function));
return vector_tanh_function;
diff --git a/tensorflow/compiler/xla/service/cpu/sample_harness.cc b/tensorflow/compiler/xla/service/cpu/sample_harness.cc
index d9e8dcaed9..f227e4ae13 100644
--- a/tensorflow/compiler/xla/service/cpu/sample_harness.cc
+++ b/tensorflow/compiler/xla/service/cpu/sample_harness.cc
@@ -21,8 +21,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/client_library.h"
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/compiler/xla/types.h"
diff --git a/tensorflow/compiler/xla/service/cpu/tests/BUILD b/tensorflow/compiler/xla/service/cpu/tests/BUILD
index b4c33e2f6c..181cec3cdd 100644
--- a/tensorflow/compiler/xla/service/cpu/tests/BUILD
+++ b/tensorflow/compiler/xla/service/cpu/tests/BUILD
@@ -135,9 +135,9 @@ tf_cc_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:arithmetic",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/compiler/xla/service:cpu_plugin",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
diff --git a/tensorflow/compiler/xla/service/cpu/tests/cpu_infeed_test.cc b/tensorflow/compiler/xla/service/cpu/tests/cpu_infeed_test.cc
index 0d45918d09..c433bddc84 100644
--- a/tensorflow/compiler/xla/service/cpu/tests/cpu_infeed_test.cc
+++ b/tensorflow/compiler/xla/service/cpu/tests/cpu_infeed_test.cc
@@ -19,8 +19,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/statusor.h"
diff --git a/tensorflow/compiler/xla/service/elemental_ir_emitter.cc b/tensorflow/compiler/xla/service/elemental_ir_emitter.cc
index b58b87a978..f883eb828c 100644
--- a/tensorflow/compiler/xla/service/elemental_ir_emitter.cc
+++ b/tensorflow/compiler/xla/service/elemental_ir_emitter.cc
@@ -1223,168 +1223,254 @@ llvm_ir::IrArray::Index ElementalIrEmitter::ElementwiseSourceIndex(
return source_index;
}
-llvm_ir::ElementGenerator ElementalIrEmitter::MakeRngElementGenerator(
+StatusOr<llvm::Value*> ElementalIrEmitter::ConvertValueForDistribution(
const HloInstruction* hlo,
- const ElementalIrEmitter::HloToElementGeneratorMap& operand_to_generator)
- const {
- PrimitiveType param_prim_type = hlo->operand(0)->shape().element_type();
- llvm::Type* param_ir_type =
- llvm_ir::PrimitiveTypeToIrType(param_prim_type, module_);
-
- // Same values as PCG library
- // https://github.com/imneme/pcg-c/blob/master/include/pcg_variants.h
- llvm::Value* multiplier =
- b_->getInt(llvm::APInt(128, {0x4385DF649FCCF645, 0x2360ED051FC65DA4}));
- llvm::Value* increment =
- b_->getInt(llvm::APInt(128, {0x14057B7EF767814F, 0x5851F42D4C957F2D}));
-
- auto random_value_from_hlo = [hlo]() {
- const HloModule* module =
- hlo->IsFused() ? hlo->parent()->FusionInstruction()->parent()->parent()
- : hlo->parent()->parent();
- return module->RandomNew64();
- };
+ const ElementalIrEmitter::HloToElementGeneratorMap& operand_to_generator,
+ const llvm_ir::IrArray::Index& index, llvm::Value* raw_value) const {
+ TF_ASSIGN_OR_RETURN(llvm::Value * a_or_mean,
+ operand_to_generator.at(hlo->operand(0))(index));
+ TF_ASSIGN_OR_RETURN(llvm::Value * b_or_sigma,
+ operand_to_generator.at(hlo->operand(1))(index));
+ PrimitiveType elem_prim_ty = hlo->shape().element_type();
+ llvm::Type* elem_ir_ty =
+ llvm_ir::PrimitiveTypeToIrType(elem_prim_ty, module_);
+ llvm::Type* raw_value_ty = raw_value->getType();
+
+ // Convert raw integer to float in range [0, 1) if the element is a float.
+ llvm::Value* elem_value = raw_value;
+ if (elem_ir_ty->isFloatingPointTy()) {
+ elem_value = b_->CreateUIToFP(elem_value, elem_ir_ty);
+ unsigned raw_value_size_in_bits = raw_value_ty->getPrimitiveSizeInBits();
+ CHECK(raw_value_size_in_bits == 32 || raw_value_size_in_bits == 64);
+ elem_value = b_->CreateFDiv(
+ elem_value,
+ llvm::ConstantFP::get(elem_ir_ty,
+ raw_value_size_in_bits == 64 ? 0x1p64 : 0x1p32));
+ }
+
+ // Convert the value for the requested distribution.
+ switch (hlo->random_distribution()) {
+ case RNG_UNIFORM: {
+ if (elem_ir_ty->isFloatingPointTy()) {
+ return b_->CreateFAdd(
+ b_->CreateFMul(b_->CreateFSub(b_or_sigma, a_or_mean), elem_value),
+ a_or_mean);
+ } else {
+ // To generate a uniform random value in [a, b) from a raw random sample
+ // in range [0, 2^N), we let range = b - a and return
+ // (a + raw_value % range). If range is not a power of 2, raw values
+ // larger than (2^N - 2^N % range) are biased toward results in
+ // [a, a + (limit % range)). An unbiased algorithm would need to drop
+ // raw values and re-sample, but we don't do this because re-sampling in
+ // an efficient way is complex, and it's not clear that users need it.
+ // In particular, if one thread in a GPU warp needs to re-sample, we pay
+ // the same cost as if the whole warp were to re-sample. So an
+ // efficient re-sampling implementation on GPU would need to do
+ // nontrivial work to share entropy between threads in the warp.
+ auto range = b_->CreateSub(b_or_sigma, a_or_mean);
+ return b_->CreateAdd(a_or_mean, b_->CreateURem(elem_value, range));
+ }
+ }
+ case RNG_NORMAL: {
+ TF_ASSIGN_OR_RETURN(
+ llvm::Value * r,
+ EmitErfcInv(elem_prim_ty,
+ b_->CreateFMul(llvm::ConstantFP::get(elem_ir_ty, 2.0),
+ elem_value)));
+ return b_->CreateFAdd(b_->CreateFMul(r, b_or_sigma), a_or_mean);
+ }
+ default:
+ return InvalidArgument(
+ "unhandled distribution %s",
+ RandomDistribution_Name(hlo->random_distribution()).c_str());
+ }
+}
+
+namespace {
+
+// Checks that the primitive type is supported by the elemental IR emitter for
+// Philox RNG and returns the number of elements in each 128 bit sample of the
+// Philox RNG algorithm.
+int32 GetNumberOfElementsPerPhiloxRngSample(PrimitiveType elem_prim_ty) {
+ // Calculate the number of elements, that is the number of random numbers, in
+ // a 128 bit sample.
+ switch (elem_prim_ty) {
+ case U32:
+ case S32:
+ case F32:
+ // The algorithm uses 32 bits to generate values for F16.
+ case F16:
+ return 4;
+ case U64:
+ case F64:
+ return 2;
+ default:
+ // BF16 is converted to F16 by the hlo pass HloElementTypeConverter.
+ // Other data types are not supported by XLA random operation.
+ LOG(FATAL) << "Unrecognized primitive type for RNG " << elem_prim_ty;
+ }
+ return 0;
+}
- // Seed each RNG emitter with a new 64-bit seed from the HloModule. If the
- // compilation order is deterministic (i.e., RandomNew64 invocation order is
- // deterministic), then the order of RNG is deterministic for a given seed and
- // hence tests will be deterministic.
- // If the user provides a global seed instruction then we only use 64-bits of
- // the host's random number generator to seed the 128 bit value with the other
- // 64-bits is due to a user specified global seed instruction.
- // Create a GlobalVariable to maintain state between invocations. There is a
- // bug in NVPTX with GlobalVariable and 128 bit values, so using 2 64-bit
+// Calculates the four uint32 values for the 128-bit Philox sample.
+std::array<llvm::Value*, 4> CalculateSampleValues(
+ llvm::Value* sample_idx, llvm::Value* hlo_random_value,
+ llvm::Value* global_random_number, llvm::Value* rng_state,
+ llvm::IRBuilder<>* b) {
+ llvm::Type* index_ty = sample_idx->getType();
+
+ std::array<llvm::Value*, 4> counter_values;
+
+ // Use the sample index to initialize counter[0] and counter[1].
+ unsigned index_ty_size_in_bits = index_ty->getPrimitiveSizeInBits();
+ CHECK(index_ty_size_in_bits == 32 || index_ty_size_in_bits == 64);
+ if (index_ty_size_in_bits == 32) {
+ counter_values[0] = sample_idx;
+ counter_values[1] = b->getInt32(0);
+ } else {
+ std::tie(counter_values[0], counter_values[1]) =
+ llvm_ir::SplitInt64ToInt32s(b, sample_idx);
+ }
+
+ // Xor the global state variable with the global random number seed and use
+ // the result to initialize counter[2] and counter[3].
+ std::tie(counter_values[2], counter_values[3]) = llvm_ir::SplitInt64ToInt32s(
+ b, b->CreateXor(rng_state, global_random_number));
+
+ // The algorithm uses a 64 bit key, which is also interpreted as two uint32
// values.
- llvm::GlobalVariable* state_ptr0 = new llvm::GlobalVariable(
- /*M=*/*module_,
- /*Ty=*/b_->getInt64Ty(),
- /*isConstant=*/false,
- /*Linkage=*/llvm::GlobalValue::PrivateLinkage,
- /*Initializer=*/b_->getInt64(random_value_from_hlo()),
- /*Name=*/"state_ptr0");
-
- // When the module config seed is 0, the expected result of a prng is a random
- // value. Instead of using the random_value_from_hlo, we need a global random
- // value as the graph seed. This is because if we use random_value_from_hlo
- // here, then for a newly built hlo graph, it always gives the same number.
- uint64 graph_seed = hlo_module_config_.seed() != 0 ? hlo_module_config_.seed()
- : GlobalRandomValue();
- llvm::GlobalVariable* state_ptr1 = new llvm::GlobalVariable(
- /*M=*/*module_,
- /*Ty=*/b_->getInt64Ty(),
- /*isConstant=*/false,
- /*Linkage=*/llvm::GlobalValue::PrivateLinkage,
- /*Initializer=*/b_->getInt64(graph_seed),
- /*Name=*/"state_ptr1");
-
- // We want each thread to use its own stream, so we modify the increment per
- // thread. We want the increment to remain odd, so we shift the thread id left
- // 1 and add it to the increment.
- increment = b_->CreateAdd(increment, b_->CreateShl(EmitThreadId(), 1));
-
- // PCG-XSL-RR algorithm
- // http://www.pcg-random.org/pdf/toms-oneill-pcg-family-v1.02.pdf
- // state = multiplier * state + increment
- // return uint64_t(state ^ (state >> 64))) >>> (state >> 122)
- // where ">>>" is bitwise rotation
- auto get_next_i64 = [=]() {
- llvm::Value* state0 = b_->CreateZExtOrTrunc(
- b_->CreateLoad(state_ptr0, "state0"), b_->getInt128Ty());
- llvm::Value* state1 = b_->CreateShl(
- b_->CreateZExtOrTrunc(b_->CreateLoad(state_ptr1, "state1"),
- b_->getInt128Ty()),
- 64);
- llvm::Value* state = b_->CreateOr(state0, state1);
- llvm::Value* updated =
- b_->CreateAdd(b_->CreateMul(state, multiplier), increment);
- b_->CreateStore(b_->CreateTrunc(updated, b_->getInt64Ty()), state_ptr0);
- b_->CreateStore(
- b_->CreateTrunc(b_->CreateLShr(updated, 64), b_->getInt64Ty()),
- state_ptr1);
-
- return llvm_ir::CreateRor(
- b_->CreateTrunc(b_->CreateXor(state, b_->CreateLShr(state, 64)),
- b_->getInt64Ty()),
- b_->CreateTrunc(b_->CreateLShr(state, 122), b_->getInt64Ty()), b_);
- };
+ llvm::Value* key_values[2];
+
+ // Use a module random number to initialize the key.
+ std::tie(key_values[0], key_values[1]) =
+ llvm_ir::SplitInt64ToInt32s(b, hlo_random_value);
+
+ // Prepare the constants used in the Philox RNG Algorithm.
+ llvm::Value* philoxW32A = b->getInt32(0x9E3779B9);
+ llvm::Value* philoxW32B = b->getInt32(0xBB67AE85);
+ llvm::Value* philoxM4xW32A = b->getInt32(0xD2511F53);
+ llvm::Value* philoxM4xW32B = b->getInt32(0xCD9E8D57);
+
+ // Compute the 128 bit value for the current sample by repeating the
+ // single round computation and key raising computation for ten times.
+ for (int round = 0; round < 10; ++round) {
+ // A single round of computation of the counter values is as follows:
+ // MultiplyHighLow(kPhiloxM4x32A, counter[0], &lo0, &hi0);
+ // MultiplyHighLow(kPhiloxM4x32B, counter[2], &lo1, &hi1);
+ // counter[0] = hi1 ^ counter[1] ^ key[0];
+ // counter[1] = lo1;
+ // counter[2] = hi0 ^ counter[3] ^ key[1];
+ // counter[3] = lo0;
+ llvm::Value* lo0;
+ llvm::Value* hi0;
+ std::tie(lo0, hi0) =
+ llvm_ir::UMulLowHigh32(b, philoxM4xW32A, counter_values[0]);
+ llvm::Value* lo1;
+ llvm::Value* hi1;
+ std::tie(lo1, hi1) =
+ llvm_ir::UMulLowHigh32(b, philoxM4xW32B, counter_values[2]);
+ counter_values[0] =
+ b->CreateXor(hi1, b->CreateXor(counter_values[1], key_values[0]));
+ counter_values[1] = lo1;
+ counter_values[2] =
+ b->CreateXor(hi0, b->CreateXor(counter_values[3], key_values[1]));
+ counter_values[3] = lo0;
+ key_values[0] = b->CreateAdd(key_values[0], philoxW32A);
+ key_values[1] = b->CreateAdd(key_values[1], philoxW32B);
+ }
- auto get_next_uniform_float = [=]() {
- return b_->CreateFDiv(b_->CreateUIToFP(get_next_i64(), param_ir_type),
- llvm::ConstantFP::get(param_ir_type, 0x1p64));
- };
+ return counter_values;
+}
+} // namespace
+
+// Implements the Philox algorithm to generate random numbers in parallel.
+// Salmon et al. SC 2011. Parallel random numbers: as easy as 1, 2, 3.
+// http://www.thesalmons.org/john/random123/papers/random123sc11.pdf
+//
+// The paper presents a few variants of the Philox algorithm, we picked the
+// 4x32_10 version of the algorithm for the following reasons:
+// . 4x32 uses 32-bit multiplication which is fast on GPUs.
+// . The authors recommend the 10-round variant, and TensorFlow also uses it.
+//
+// Precondition: the RNG instruction is not fused.
+llvm_ir::ElementGenerator ElementalIrEmitter::MakePhiloxRngElementGenerator(
+ const HloInstruction* hlo,
+ const ElementalIrEmitter::HloToElementGeneratorMap& operand_to_generator)
+ const {
+ VLOG(3) << "Using philox RNG algorithm";
+ CHECK(!hlo->IsFused());
+ // A random number generated by the per module random number generator.
+ // This ensures that each RNG HLO generates a different random sequence.
+ llvm::Value* hlo_random_value = b_->getInt64(hlo->GetModule()->RandomNew64());
+ // A value specified by the configuration or generated by a global random
+ // number generator.
+ llvm::Value* global_random_number =
+ b_->getInt64(hlo_module_config_.seed() != 0 ? hlo_module_config_.seed()
+ : GlobalRandomValue());
+
+ int elems_per_sample =
+ GetNumberOfElementsPerPhiloxRngSample(hlo->shape().element_type());
+
+ // Allocate stack storage for the 128 bit sample as four int32.
+ llvm::Type* int32_ty = b_->getInt32Ty();
+ llvm::Value* sample_address = llvm_ir::EmitAllocaAtFunctionEntryWithCount(
+ int32_ty, /*element_count=*/b_->getInt32(4), "sample", b_);
+
+ // Load the global state variable for the Philox RNG algorithm.
+ llvm::GlobalVariable* rng_state_ptr =
+ llvm_ir::GetOrCreateVariableForPhiloxRngState(module_, b_);
+ llvm::Value* rng_state = b_->CreateLoad(rng_state_ptr, "rng_state_value");
+
+ // Build and return the elemental IR generator to generate a random value for
+ // the element corresponding to the current thread.
+ //
+ // This elemental IR generator computes one sample with multiple random
+ // numbers but only returns one random number. As a result, neighboring
+ // threads may calculate the same sample unnecessarily. However, if the
+ // kernel containing the RNG hlo is unrolled, LLVM is able to optimize away
+ // the duplicated computation of the same sample. In particular, if the unroll
+ // factor is a multiplier of elems_per_sample, LLVM is able to completely
+ // remove such duplicated computation. If the unroll factor is a non-trivial
+ // factor of elems_per_sample, LLVM can only partially remove such duplicated
+ // computation.
return [=](const llvm_ir::IrArray::Index& index) -> StatusOr<llvm::Value*> {
- switch (hlo->random_distribution()) {
- case RNG_UNIFORM: {
- TF_ASSIGN_OR_RETURN(llvm::Value * p,
- operand_to_generator.at(hlo->operand(0))(index));
- TF_ASSIGN_OR_RETURN(llvm::Value * q,
- operand_to_generator.at(hlo->operand(1))(index));
- if (primitive_util::IsFloatingPointType(param_prim_type)) {
- return b_->CreateFAdd(
- b_->CreateFMul(b_->CreateFSub(q, p), get_next_uniform_float()),
- p);
- } else {
- auto r = b_->CreateSub(q, p);
- auto leading_zeros = llvm_ir::EmitCallToIntrinsic(
- llvm::Intrinsic::ctlz, {r, b_->getInt1(true)}, {param_ir_type},
- b_);
- auto in_block = b_->GetInsertBlock();
-
- // A terminator should be present iff we're emitting code
- // into the middle (as opposed to the end) of a basic block.
- CHECK_EQ(b_->GetInsertPoint() == in_block->end(),
- in_block->getTerminator() == nullptr);
-
- llvm::BasicBlock* body_block;
- llvm::BasicBlock* out_block;
-
- if (b_->GetInsertPoint() == in_block->end()) {
- body_block =
- llvm_ir::CreateBasicBlock(nullptr, IrName(hlo, "rng_body"), b_);
- out_block =
- llvm_ir::CreateBasicBlock(nullptr, IrName(hlo, "rng_out"), b_);
- llvm::BranchInst::Create(body_block, in_block);
- } else {
- body_block =
- in_block->splitBasicBlock(b_->GetInsertPoint(), "rng_body");
- out_block =
- body_block->splitBasicBlock(b_->GetInsertPoint(), "rng_out");
- body_block->getTerminator()->eraseFromParent();
- }
-
- SetToFirstInsertPoint(body_block, b_);
- auto random = b_->CreateAnd(
- b_->CreateZExtOrTrunc(get_next_i64(), param_ir_type),
- b_->CreateLShr(llvm::ConstantInt::get(param_ir_type, ~0),
- leading_zeros));
- llvm::BranchInst::Create(out_block, body_block,
- b_->CreateICmpULT(random, r), body_block);
- SetToFirstInsertPoint(out_block, b_);
- return b_->CreateAdd(
- p, b_->CreateSelect(b_->CreateICmpEQ(p, q),
- llvm::ConstantInt::get(param_ir_type, 0),
- random));
- }
- }
- case RNG_NORMAL: {
- TF_ASSIGN_OR_RETURN(llvm::Value * m,
- operand_to_generator.at(hlo->operand(0))(index));
- TF_ASSIGN_OR_RETURN(llvm::Value * s,
- operand_to_generator.at(hlo->operand(1))(index));
- TF_ASSIGN_OR_RETURN(
- llvm::Value * r,
- EmitErfcInv(
- param_prim_type,
- b_->CreateFMul(llvm::ConstantFP::get(param_ir_type, 2.0),
- get_next_uniform_float())));
- return b_->CreateFAdd(b_->CreateFMul(r, s), m);
- }
- default:
- return InvalidArgument(
- "unhandled distribution %s",
- RandomDistribution_Name(hlo->random_distribution()).c_str());
+ llvm::Type* index_ty = index.GetType();
+ // Calculate the linear element index.
+ llvm::Value* elem_idx = index.linear();
+ if (elem_idx == nullptr) {
+ elem_idx = index.Linearize(AsInt64Slice(hlo->shape().dimensions()), b_);
}
+
+ // Calculate the index for the 128 bit sample and the offset of the current
+ // element within the sample.
+ llvm::Value* elems_per_sample_value =
+ llvm::ConstantInt::get(index_ty, elems_per_sample);
+ llvm::Value* sample_idx = b_->CreateUDiv(elem_idx, elems_per_sample_value);
+ llvm::Value* elem_offset = b_->CreateURem(elem_idx, elems_per_sample_value);
+
+ std::array<llvm::Value*, 4> counter_values = CalculateSampleValues(
+ sample_idx, hlo_random_value, global_random_number, rng_state, b_);
+
+ // Store the four counter_values into the sample_address alloca so we can
+ // load the elem_offset'th one below.
+ for (int idx = 0; idx < 4; ++idx) {
+ b_->CreateStore(counter_values[idx],
+ b_->CreateInBoundsGEP(sample_address, b_->getInt32(idx)));
+ }
+
+ llvm::Type* int64_ty = b_->getInt64Ty();
+ CHECK(elems_per_sample == 2 || elems_per_sample == 4);
+ llvm::Type* raw_value_ty = elems_per_sample == 2 ? int64_ty : int32_ty;
+ // Retrieve the raw value for the current element from the current sample.
+ llvm::Value* raw_elem_value = b_->CreateLoad(
+ b_->CreateInBoundsGEP(
+ b_->CreatePointerCast(sample_address, raw_value_ty->getPointerTo()),
+ elem_offset),
+ "raw_elem_value");
+
+ return ConvertValueForDistribution(hlo, operand_to_generator, index,
+ raw_elem_value);
};
}
@@ -1517,10 +1603,6 @@ StatusOr<llvm::Value*> ElementalIrEmitter::EmitElementalDynamicSlice(
// Clamp the start index so that the sliced portion fits in the operand:
// start_index = clamp(start_index, 0, operand_dim_size - output_dim_size)
-
- // TODO(b/74360564): This is implementation defined behavior, but is
- // currently respected by all implementations. Change this if we ever decide
- // to officially document different behavior.
start_index_value = b_->CreateSExtOrTrunc(start_index_value, index_type);
int64 largest_valid_start_index =
input_hlo->shape().dimensions(i) - hlo->shape().dimensions(i);
@@ -1671,10 +1753,6 @@ StatusOr<llvm::Value*> ElementalIrEmitter::EmitElementalDynamicUpdateSlice(
// Clamp the start index so that the update region fits in the operand.
// start_index = clamp(start_index, 0, input_dim_size - update_dim_size)
-
- // TODO(b/74360564): This is implementation defined behavior, but is
- // currently respected by all implementations. Change this if we ever decide
- // to officially document different behavior.
start_index_value = b_->CreateSExtOrTrunc(start_index_value, index_type);
llvm::Value* update_dim_size =
index_typed_const(update_hlo->shape().dimensions(i));
@@ -2042,7 +2120,7 @@ llvm_ir::ElementGenerator ElementalIrEmitter::MakeElementGenerator(
hlo->shape(), hlo->operand(0)->shape(), hlo->dimensions(), b_));
};
case HloOpcode::kRng:
- return MakeRngElementGenerator(hlo, operand_to_generator);
+ return MakePhiloxRngElementGenerator(hlo, operand_to_generator);
case HloOpcode::kPad:
return [this, hlo, &operand_to_generator](
const IrArray::Index& padded_index) -> StatusOr<llvm::Value*> {
diff --git a/tensorflow/compiler/xla/service/elemental_ir_emitter.h b/tensorflow/compiler/xla/service/elemental_ir_emitter.h
index deba6bea0a..fcb34557a5 100644
--- a/tensorflow/compiler/xla/service/elemental_ir_emitter.h
+++ b/tensorflow/compiler/xla/service/elemental_ir_emitter.h
@@ -193,10 +193,17 @@ class ElementalIrEmitter {
const HloModuleConfig& hlo_module_config_;
private:
- // Returns a ElementGenerator for a RNG HloInstruction.
- llvm_ir::ElementGenerator MakeRngElementGenerator(
+ // Returns a ElementGenerator for an RNG HloInstruction using the Philox
+ // random number generation algorithm.
+ llvm_ir::ElementGenerator MakePhiloxRngElementGenerator(
const HloInstruction* hlo,
const HloToElementGeneratorMap& operand_to_generator) const;
+ // Converts the raw value generated by a random number generation algorithm
+ // to the distribution requested by the RNG HloInstruction.
+ StatusOr<llvm::Value*> ConvertValueForDistribution(
+ const HloInstruction* hlo,
+ const ElementalIrEmitter::HloToElementGeneratorMap& operand_to_generator,
+ const llvm_ir::IrArray::Index& index, llvm::Value* raw_value) const;
};
} // namespace xla
diff --git a/tensorflow/compiler/xla/service/execution_tracker.cc b/tensorflow/compiler/xla/service/execution_tracker.cc
index 6794cfe297..228c3fac95 100644
--- a/tensorflow/compiler/xla/service/execution_tracker.cc
+++ b/tensorflow/compiler/xla/service/execution_tracker.cc
@@ -25,7 +25,7 @@ limitations under the License.
namespace xla {
AsyncExecution::AsyncExecution(Backend* backend,
- std::vector<Backend::StreamPtr> streams,
+ std::vector<StreamPool::Ptr> streams,
const ExecutionProfile& profile,
GlobalDataHandle result)
: backend_(CHECK_NOTNULL(backend)),
@@ -46,9 +46,10 @@ Status AsyncExecution::BlockUntilDone() const {
ExecutionTracker::ExecutionTracker() : next_handle_(1) {}
-ExecutionHandle ExecutionTracker::Register(
- Backend* backend, std::vector<Backend::StreamPtr> streams,
- const ExecutionProfile& profile, GlobalDataHandle result) {
+ExecutionHandle ExecutionTracker::Register(Backend* backend,
+ std::vector<StreamPool::Ptr> streams,
+ const ExecutionProfile& profile,
+ GlobalDataHandle result) {
tensorflow::mutex_lock lock(execution_mutex_);
int64 handle = next_handle_++;
auto inserted = handle_to_execution_.emplace(
diff --git a/tensorflow/compiler/xla/service/execution_tracker.h b/tensorflow/compiler/xla/service/execution_tracker.h
index 4458152dd9..4e9b9f883e 100644
--- a/tensorflow/compiler/xla/service/execution_tracker.h
+++ b/tensorflow/compiler/xla/service/execution_tracker.h
@@ -22,7 +22,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/executable_run_options.h"
#include "tensorflow/compiler/xla/service/backend.h"
-#include "tensorflow/compiler/xla/service/pool.h"
+#include "tensorflow/compiler/xla/service/stream_pool.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/compiler/xla/util.h"
#include "tensorflow/compiler/xla/xla_data.pb.h"
@@ -40,7 +40,7 @@ namespace xla {
// the stream when destructed.
class AsyncExecution {
public:
- AsyncExecution(Backend* backend, std::vector<Backend::StreamPtr> streams,
+ AsyncExecution(Backend* backend, std::vector<StreamPool::Ptr> streams,
const ExecutionProfile& profile, GlobalDataHandle result);
Status BlockUntilDone() const;
@@ -54,7 +54,7 @@ class AsyncExecution {
Backend* backend_;
// Stream on which the execution is launched.
- std::vector<Backend::StreamPtr> streams_;
+ std::vector<StreamPool::Ptr> streams_;
// Profile object of the execution to be returned to the user.
ExecutionProfile profile_;
@@ -72,7 +72,7 @@ class ExecutionTracker {
// Registers an execution with its backend, streams, and data handle to the
// execution result. Returns a handle for the registered execution.
ExecutionHandle Register(Backend* backend,
- std::vector<Backend::StreamPtr> stream,
+ std::vector<StreamPool::Ptr> stream,
const ExecutionProfile& profile,
GlobalDataHandle data);
diff --git a/tensorflow/compiler/xla/service/gpu/BUILD b/tensorflow/compiler/xla/service/gpu/BUILD
index 72aff197fc..e0aae3866b 100644
--- a/tensorflow/compiler/xla/service/gpu/BUILD
+++ b/tensorflow/compiler/xla/service/gpu/BUILD
@@ -114,11 +114,13 @@ cc_library(
srcs = ["hlo_to_ir_bindings.cc"],
hdrs = ["hlo_to_ir_bindings.h"],
deps = [
+ ":buffer_allocations",
":ir_emission_utils",
"//tensorflow/compiler/xla:util",
"//tensorflow/compiler/xla/service:buffer_assignment",
"//tensorflow/compiler/xla/service:hlo",
"//tensorflow/compiler/xla/service/llvm_ir:alias_analysis",
+ "//tensorflow/compiler/xla/service/llvm_ir:buffer_assignment_util",
"//tensorflow/compiler/xla/service/llvm_ir:ir_array",
"//tensorflow/compiler/xla/service/llvm_ir:llvm_util",
"//tensorflow/compiler/xla/service/llvm_ir:tuple_ops",
@@ -142,6 +144,7 @@ cc_library(
],
deps = [
":backend_configs",
+ ":buffer_allocations",
":cudnn_convolution_runner",
":elemental_ir_emitter",
":gpu_constants",
@@ -163,6 +166,7 @@ cc_library(
"//tensorflow/compiler/xla/service:elemental_ir_emitter",
"//tensorflow/compiler/xla/service:hlo",
"//tensorflow/compiler/xla/service:name_uniquer",
+ "//tensorflow/compiler/xla/service/llvm_ir:buffer_assignment_util",
"//tensorflow/compiler/xla/service/llvm_ir:dynamic_update_slice_util",
"//tensorflow/compiler/xla/service/llvm_ir:fused_ir_emitter",
"//tensorflow/compiler/xla/service/llvm_ir:ir_array",
@@ -217,6 +221,7 @@ cc_library(
"//tensorflow/compiler/xla/service/llvm_ir:llvm_loop",
"//tensorflow/compiler/xla/service/llvm_ir:llvm_util",
"//tensorflow/compiler/xla/service/llvm_ir:loop_emitter",
+ "//tensorflow/compiler/xla/service/llvm_ir:math_ops",
"//tensorflow/core:lib",
"@llvm//:core",
"@llvm//:support",
@@ -247,7 +252,7 @@ cc_library(
deps = [
"//tensorflow/compiler/xla/service:hlo",
"//tensorflow/compiler/xla/service:hlo_execution_profile",
- "//tensorflow/compiler/xla/service:pool",
+ "//tensorflow/compiler/xla/service:stream_pool",
"//tensorflow/core:lib",
"//tensorflow/core:ptr_util",
"//tensorflow/core:stream_executor_no_cuda",
@@ -322,6 +327,7 @@ cc_library(
"//tensorflow/compiler/xla/service:shaped_buffer",
"//tensorflow/compiler/xla/service:transfer_manager",
"//tensorflow/compiler/xla/service:tuple_points_to_analysis",
+ "//tensorflow/compiler/xla/service/llvm_ir:buffer_assignment_util",
"//tensorflow/core:lib",
"//tensorflow/core:lib_internal",
"//tensorflow/core:stream_executor_no_cuda",
@@ -451,6 +457,7 @@ cc_library(
srcs = ["multi_output_fusion.cc"],
hdrs = ["multi_output_fusion.h"],
deps = [
+ ":instruction_fusion",
":ir_emission_utils",
"//tensorflow/compiler/xla:shape_util",
"//tensorflow/compiler/xla/service:hlo",
@@ -463,6 +470,7 @@ tf_cc_test(
name = "multi_output_fusion_test",
srcs = ["multi_output_fusion_test.cc"],
deps = [
+ ":instruction_fusion",
":multi_output_fusion",
"//tensorflow/compiler/xla:status_macros",
"//tensorflow/compiler/xla:util",
@@ -537,6 +545,38 @@ cc_library(
)
cc_library(
+ name = "pad_for_tensor_cores",
+ srcs = ["pad_for_tensor_cores.cc"],
+ hdrs = ["pad_for_tensor_cores.h"],
+ deps = [
+ ":ir_emission_utils",
+ "//tensorflow/compiler/xla:literal",
+ "//tensorflow/compiler/xla:literal_util",
+ "//tensorflow/compiler/xla:util",
+ "//tensorflow/compiler/xla:window_util",
+ "//tensorflow/compiler/xla:xla_data_proto",
+ "//tensorflow/compiler/xla/service:hlo_creation_utils",
+ "//tensorflow/compiler/xla/service:hlo_pass",
+ "//tensorflow/compiler/xla/service:shape_inference",
+ ],
+)
+
+tf_cc_test(
+ name = "pad_for_tensor_cores_test",
+ srcs = ["pad_for_tensor_cores_test.cc"],
+ deps = [
+ ":ir_emission_utils",
+ ":pad_for_tensor_cores",
+ "//tensorflow/compiler/xla:status_macros",
+ "//tensorflow/compiler/xla:util",
+ "//tensorflow/compiler/xla/service:hlo_matchers",
+ "//tensorflow/compiler/xla/service:hlo_parser",
+ "//tensorflow/compiler/xla/tests:hlo_verified_test_base",
+ "//tensorflow/compiler/xla/tests:xla_internal_test_main", # build_cleaner: keep
+ ],
+)
+
+cc_library(
name = "gpu_transfer_manager",
srcs = ["gpu_transfer_manager.cc"],
hdrs = ["gpu_transfer_manager.h"],
@@ -580,9 +620,11 @@ cc_library(
":ir_emission_utils",
":ir_emitter",
":multi_output_fusion",
+ ":pad_for_tensor_cores",
":pad_insertion",
":partition_assignment",
":stream_assignment",
+ ":stream_executor_util",
"//tensorflow/compiler/xla:protobuf_util",
"//tensorflow/compiler/xla:status_macros",
"//tensorflow/compiler/xla:statusor",
@@ -806,6 +848,7 @@ cc_library(
deps = [
"//tensorflow/compiler/xla:shape_util",
"//tensorflow/compiler/xla:statusor",
+ "//tensorflow/compiler/xla:types",
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/core:stream_executor_no_cuda",
],
diff --git a/tensorflow/compiler/xla/service/gpu/buffer_allocations.cc b/tensorflow/compiler/xla/service/gpu/buffer_allocations.cc
index b095d4cd73..537295292b 100644
--- a/tensorflow/compiler/xla/service/gpu/buffer_allocations.cc
+++ b/tensorflow/compiler/xla/service/gpu/buffer_allocations.cc
@@ -44,12 +44,22 @@ StatusOr<std::unique_ptr<BufferAllocations>> BufferAllocations::Builder::Build(
num_buffers, device_ordinal, memory_allocator, buffer_assignment));
for (BufferAllocation::Index i = 0; i < num_buffers; ++i) {
+ const BufferAllocation& allocation = buffer_assignment->GetAllocation(i);
+ const int64 expected_alignment = [&] {
+ if (allocation.is_entry_computation_parameter()) {
+ return kEntryParameterAlignBytes;
+ } else if (allocation.is_constant()) {
+ return kConstantBufferAlignBytes;
+ } else {
+ return kXlaAllocatedBufferAlignBytes;
+ }
+ }();
+
// If buffer #i's address is already registered (e.g. external arguments or
// result buffers), use that registered buffer.
if (registered_buffers_.count(i)) {
se::DeviceMemoryBase address = FindOrDie(registered_buffers_, i);
- if (reinterpret_cast<uintptr_t>(address.opaque()) %
- kEntryParameterAlignBytes !=
+ if (reinterpret_cast<uintptr_t>(address.opaque()) % expected_alignment !=
0) {
return InternalError(
"Address of registered buffer %lld must be a multiple of %llx, but "
@@ -62,7 +72,6 @@ StatusOr<std::unique_ptr<BufferAllocations>> BufferAllocations::Builder::Build(
// Allocate each allocation that might escape, or is the temp buffer.
bool seen_temp_buffer = false;
- const BufferAllocation& allocation = buffer_assignment->GetAllocation(i);
if (allocation.maybe_live_out() || allocation.IsPreallocatedTempBuffer()) {
const int64 buffer_size = allocation.size();
se::DeviceMemoryBase buffer_address;
@@ -70,8 +79,7 @@ StatusOr<std::unique_ptr<BufferAllocations>> BufferAllocations::Builder::Build(
OwningDeviceMemory buffer;
TF_ASSIGN_OR_RETURN(
buffer, memory_allocator->Allocate(device_ordinal, buffer_size));
- if (reinterpret_cast<uintptr_t>(buffer.opaque()) %
- kXlaAllocatedBufferAlignBytes !=
+ if (reinterpret_cast<uintptr_t>(buffer.opaque()) % expected_alignment !=
0) {
return InternalError(
"Address returned by memory_allocator->Allocate must be a "
@@ -165,5 +173,10 @@ void BufferAllocations::SetBuffer(BufferAllocation::Index buffer_index,
buffers_[buffer_index] = buffer;
}
+bool ShouldEmitLiteralInLlvmIr(const Literal& literal) {
+ // LLVM can sometimes do interesting optimizations using scalar constants.
+ return ShapeUtil::IsScalar(literal.shape());
+}
+
} // namespace gpu
} // namespace xla
diff --git a/tensorflow/compiler/xla/service/gpu/buffer_allocations.h b/tensorflow/compiler/xla/service/gpu/buffer_allocations.h
index 6366235025..f13eab0dd7 100644
--- a/tensorflow/compiler/xla/service/gpu/buffer_allocations.h
+++ b/tensorflow/compiler/xla/service/gpu/buffer_allocations.h
@@ -107,6 +107,12 @@ class BufferAllocations {
bool torn_down_ = false;
};
+// LLVM and PTXAS don't deal well with large constants, so we only emit very
+// small constants directly in LLVM IR. Larger constants are emitted with zero
+// initializers in LLVM IR and are later overwritten when the PTX/CUBIN is
+// loaded.
+bool ShouldEmitLiteralInLlvmIr(const Literal& literal);
+
} // namespace gpu
} // namespace xla
diff --git a/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.cc b/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.cc
index b97a627d9b..cc38db27e2 100644
--- a/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.cc
+++ b/tensorflow/compiler/xla/service/gpu/elemental_ir_emitter.cc
@@ -35,6 +35,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/service/llvm_ir/ir_array.h"
#include "tensorflow/compiler/xla/service/llvm_ir/llvm_loop.h"
#include "tensorflow/compiler/xla/service/llvm_ir/llvm_util.h"
+#include "tensorflow/compiler/xla/service/llvm_ir/math_ops.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/status_macros.h"
#include "tensorflow/compiler/xla/statusor.h"
@@ -277,6 +278,16 @@ StatusOr<llvm::Value*> GpuElementalIrEmitter::EmitFloatUnaryOp(
PrimitiveType output_type = op->shape().element_type();
switch (op->opcode()) {
case HloOpcode::kTanh:
+ // If we don't care much about precision, emit a fast approximation of
+ // tanh.
+ if (hlo_module_config_.debug_options().xla_enable_fast_math()) {
+ // Upcast F16 to F32 if necessary.
+ llvm::Type* type =
+ input_type == F16 ? b_->getFloatTy() : operand_value->getType();
+ llvm::Value* input = b_->CreateFPCast(operand_value, type);
+ llvm::Value* fast_tanh = llvm_ir::EmitFastTanh(b_, input);
+ return b_->CreateFPCast(fast_tanh, operand_value->getType());
+ }
return EmitLibdeviceMathCall("__nv_tanh", {operand_value}, {input_type},
output_type);
default:
diff --git a/tensorflow/compiler/xla/service/gpu/gemm_thunk.cc b/tensorflow/compiler/xla/service/gpu/gemm_thunk.cc
index dbc7754e25..dbcbabdc52 100644
--- a/tensorflow/compiler/xla/service/gpu/gemm_thunk.cc
+++ b/tensorflow/compiler/xla/service/gpu/gemm_thunk.cc
@@ -18,6 +18,7 @@ limitations under the License.
#include <functional>
#include "tensorflow/compiler/xla/util.h"
+#include "tensorflow/core/lib/strings/strcat.h"
#include "tensorflow/core/platform/logging.h"
#include "tensorflow/core/platform/stream_executor_no_cuda.h"
#include "tensorflow/core/platform/types.h"
@@ -141,9 +142,15 @@ StatusOr<se::blas::AlgorithmType> DoGemmAutotune(
alpha, computation_type, algorithm,
stream, &profile_result));
- if (profile_result.is_valid() && profile_result.elapsed_time_in_ms() <
- best_result.elapsed_time_in_ms()) {
- best_result = profile_result;
+ if (profile_result.is_valid()) {
+ VLOG(3) << "cublas gemm algorithm " << algorithm << " took "
+ << profile_result.elapsed_time_in_ms() << "ms";
+ if (profile_result.elapsed_time_in_ms() <
+ best_result.elapsed_time_in_ms()) {
+ best_result = profile_result;
+ }
+ } else {
+ VLOG(4) << "cublas gemm algorithm " << algorithm << " failed.";
}
}
@@ -316,9 +323,15 @@ Status GemmThunk::ExecuteOnStream(const BufferAllocations& buffer_allocations,
se::blas::ComputationType computation_type =
GetBlasComputationType(element_type);
+ auto thunk_name = [&] {
+ return hlo_instruction() != nullptr ? hlo_instruction()->ToString()
+ : "<null>";
+ };
+
const string& device_name = stream->parent()->GetDeviceDescription().name();
auto autotune_it = autotune_results_.find(device_name);
if (autotune_it == autotune_results_.end()) {
+ VLOG(3) << "Starting autotune of GemmThunk " << thunk_name();
StatusOr<se::blas::AlgorithmType> best_algorithm =
GetGemmAutotuneFn(element_type)(lhs_matrix, rhs_matrix, output_matrix,
alpha_, computation_type, stream);
@@ -326,11 +339,11 @@ Status GemmThunk::ExecuteOnStream(const BufferAllocations& buffer_allocations,
autotune_results_.insert({device_name, best_algorithm}).first;
if (autotune_it->second.ok()) {
- VLOG(2) << "Autotune on GemmThunk " << this
+ VLOG(2) << "Autotune on GemmThunk " << thunk_name()
<< " successful; best algorithm is "
<< best_algorithm.ValueOrDie();
} else {
- VLOG(2) << "Autotune on GemmThunk " << this
+ VLOG(2) << "Autotune on GemmThunk " << thunk_name()
<< " unsuccessful. Will use generic gemm.";
}
}
@@ -340,7 +353,7 @@ Status GemmThunk::ExecuteOnStream(const BufferAllocations& buffer_allocations,
if (best_algorithm.ok()) {
auto algorithm = best_algorithm.ValueOrDie();
VLOG(2) << "Using algorithm " << algorithm
- << " chosen by autotuning on GemmThunk " << this;
+ << " chosen by autotuning on GemmThunk " << thunk_name();
return GetGemmWithAlgorithmFn(element_type)(
lhs_matrix, rhs_matrix, output_matrix, alpha_, computation_type,
algorithm, stream,
diff --git a/tensorflow/compiler/xla/service/gpu/gpu_constants.cc b/tensorflow/compiler/xla/service/gpu/gpu_constants.cc
index e6ddea6d25..7f0b030fec 100644
--- a/tensorflow/compiler/xla/service/gpu/gpu_constants.cc
+++ b/tensorflow/compiler/xla/service/gpu/gpu_constants.cc
@@ -30,5 +30,7 @@ const int64 kEntryParameterAlignBytes = 16;
const int64 kXlaAllocatedBufferAlignBytes =
tensorflow::Allocator::kAllocatorAlignment;
+const int64 kConstantBufferAlignBytes = kXlaAllocatedBufferAlignBytes;
+
} // namespace gpu
} // namespace xla
diff --git a/tensorflow/compiler/xla/service/gpu/gpu_constants.h b/tensorflow/compiler/xla/service/gpu/gpu_constants.h
index 925e6927b6..6f5f1fa09c 100644
--- a/tensorflow/compiler/xla/service/gpu/gpu_constants.h
+++ b/tensorflow/compiler/xla/service/gpu/gpu_constants.h
@@ -28,6 +28,9 @@ extern const int64 kEntryParameterAlignBytes;
// out (result) buffers.
extern const int64 kXlaAllocatedBufferAlignBytes;
+// Minimum alignment for constant buffers.
+extern const int64 kConstantBufferAlignBytes;
+
} // namespace gpu
} // namespace xla
diff --git a/tensorflow/compiler/xla/service/gpu/gpu_copy_insertion.cc b/tensorflow/compiler/xla/service/gpu/gpu_copy_insertion.cc
index fbc1303085..75f414e47f 100644
--- a/tensorflow/compiler/xla/service/gpu/gpu_copy_insertion.cc
+++ b/tensorflow/compiler/xla/service/gpu/gpu_copy_insertion.cc
@@ -48,80 +48,17 @@ StatusOr<bool> GpuCopyInsertion::Run(HloModule* module) {
TF_ASSIGN_OR_RETURN(bool changed, generic_copy_insertion.Run(module));
- TF_ASSIGN_OR_RETURN(std::unique_ptr<HloDataflowAnalysis> dataflow,
- HloDataflowAnalysis::Run(*module));
-
- // Make sure all operands of a library call are in memory instead of constants
- // in IR. Also, init values of while and conditional nodes cannot be
- // constants. Insert copies for any constants found at the operands of these
- // nodes.
- tensorflow::gtl::FlatSet<HloInstruction*> inserted_copies;
+ // Check the assumption that the epsilon and feature_index constants of the
+ // CUDNN batchnorm op are not shared with other ops where we would replace
+ // them with a copy. These custom op calls are generated with the
+ // CudnnBatchNormRewriter, so this would only happen if HloCSE merges them.
for (HloComputation* computation : module->computations()) {
for (HloInstruction* hlo : computation->instructions()) {
- // Inserts a copy of hlo->operand(n) if it's a constant.
- auto copy_operand_if_constant = [&](int64 n) -> Status {
- HloInstruction* operand = hlo->mutable_operand(n);
- // Skip the operands that have already been replaced with a copy in a
- // previous iteration (which is possible when a constant is used as an
- // operand in multiple places).
- if (ContainsKey(inserted_copies, operand)) {
- return Status::OK();
- }
- for (auto& pair : dataflow->GetInstructionValueSet(operand)) {
- const HloValueSet& value_set = pair.second;
- for (const HloValue* value : value_set.values()) {
- if (value->defining_instruction()->IsConstant() &&
- !ContainsKey(hlo_to_copy_map_, value->defining_instruction())) {
- HloInstruction* constant = value->defining_instruction();
- TF_ASSIGN_OR_RETURN(HloInstruction * copy,
- FindOrInsertCopy(constant));
- TF_RETURN_IF_ERROR(constant->ReplaceAllUsesWith(copy));
- inserted_copies.insert(copy);
- changed = true;
- }
- }
- }
- return Status::OK();
- };
-
- if (IsCustomCallToDnnBatchNorm(*hlo)) {
- // The epsilon and feature_index operands to a CUDNN batchnorm op don't
- // need to be materialized in memory -- in fact, they must be constants.
- // These are the last two operands of all three batchnorm ops.
- for (int64 i = 0; i < hlo->operand_count() - 2; ++i) {
- TF_RETURN_IF_ERROR(copy_operand_if_constant(i));
- }
- } else if (ImplementedAsLibraryCall(*hlo) ||
- hlo->opcode() == HloOpcode::kCrossReplicaSum ||
- hlo->opcode() == HloOpcode::kWhile ||
- hlo->opcode() == HloOpcode::kConditional) {
- // For all other library calls, cross-replica-sum, while and conditional
- // ops materialize all the operands into memory. (Cross-replica-sum
- // gets its constant args materialized even if it's not implemented as a
- // libcall to simplify the implementation. It's slower, but we can
- // constant fold away constant args *anyway*, so we just need to make it
- // work.)
- for (int64 i = 0; i < hlo->operand_count(); ++i) {
- TF_RETURN_IF_ERROR(copy_operand_if_constant(i));
- }
+ if (!IsCustomCallToDnnBatchNorm(*hlo)) {
+ continue;
}
- }
- }
-
- if (changed) {
- // Check the assumption that the epsilon and feature_index constants of the
- // CUDNN batchnorm op are not shared with other ops where we would replace
- // them with a copy. These custom op calls are generated with the
- // CudnnBatchNormRewriter, so this would only happen if HloCSE merges them.
- for (HloComputation* computation : module->computations()) {
- for (HloInstruction* hlo : computation->instructions()) {
- if (!IsCustomCallToDnnBatchNorm(*hlo)) {
- continue;
- }
- for (int64 i = hlo->operand_count() - 2; i < hlo->operand_count();
- ++i) {
- CHECK_EQ(hlo->operand(i)->opcode(), HloOpcode::kConstant);
- }
+ for (int64 i = hlo->operand_count() - 2; i < hlo->operand_count(); ++i) {
+ CHECK_EQ(hlo->operand(i)->opcode(), HloOpcode::kConstant);
}
}
}
diff --git a/tensorflow/compiler/xla/service/gpu/gpu_executable.cc b/tensorflow/compiler/xla/service/gpu/gpu_executable.cc
index 0cad2958c7..bb71c79fd7 100644
--- a/tensorflow/compiler/xla/service/gpu/gpu_executable.cc
+++ b/tensorflow/compiler/xla/service/gpu/gpu_executable.cc
@@ -24,6 +24,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/service/gpu/buffer_allocations.h"
#include "tensorflow/compiler/xla/service/gpu/hlo_execution_profiler.h"
#include "tensorflow/compiler/xla/service/hlo_instruction.h"
+#include "tensorflow/compiler/xla/service/llvm_ir/buffer_assignment_util.h"
#include "tensorflow/compiler/xla/service/logical_buffer.h"
#include "tensorflow/compiler/xla/service/shaped_buffer.h"
#include "tensorflow/compiler/xla/service/transfer_manager.h"
@@ -84,7 +85,7 @@ Status GpuExecutable::ExecuteThunks(
}
// Stream 0 indicates `main_stream` and substreams start from stream 1.
- std::vector<Pool<se::Stream>::SmartPtr> sub_streams;
+ std::vector<StreamPool::Ptr> sub_streams;
sub_streams.reserve(thunk_schedule_->StreamCount() - 1);
while (sub_streams.size() + 1 < thunk_schedule_->StreamCount()) {
sub_streams.emplace_back();
@@ -181,6 +182,55 @@ Status GpuExecutable::ExecuteThunks(
return Status::OK();
}
+StatusOr<const GpuExecutable::BufferAllocToDeviceMemoryMap*>
+GpuExecutable::ResolveConstantGlobals(se::StreamExecutor* executor) {
+ tensorflow::mutex_lock lock(module_handle_mutex_);
+ auto it = module_globals_.find(executor);
+ if (it != module_globals_.end()) {
+ return &it->second;
+ }
+
+ se::MultiModuleLoaderSpec module_spec;
+ if (!cubin().empty()) {
+ module_spec.AddCudaCubinInMemory(cubin());
+ }
+ module_spec.AddCudaPtxInMemory(ptx().c_str());
+
+ tensorflow::gtl::FlatMap<int64, se::DeviceMemoryBase> globals;
+ se::ModuleHandle module_handle;
+ executor->LoadModule(module_spec, &module_handle);
+
+ for (BufferAllocation::Index i = 0; i < assignment_->Allocations().size();
+ ++i) {
+ const BufferAllocation& allocation = assignment_->GetAllocation(i);
+ if (allocation.is_constant()) {
+ TF_ASSIGN_OR_RETURN(
+ se::DeviceMemoryBase global,
+ executor->GetUntypedSymbol(
+ llvm_ir::ConstantBufferAllocationToGlobalName(allocation),
+ module_handle));
+ VLOG(3) << "Resolved global "
+ << llvm_ir::ConstantBufferAllocationToGlobalName(allocation)
+ << " to " << global.opaque();
+ InsertOrDie(&globals, i, global);
+
+ const Literal& literal =
+ llvm_ir::LiteralForConstantAllocation(allocation);
+ CHECK(ShapeUtil::IsArray(literal.shape()));
+ if (!ShouldEmitLiteralInLlvmIr(literal)) {
+ VLOG(3) << "H2D memcpy for constant with shape "
+ << ShapeUtil::HumanString(literal.shape());
+ TF_RETURN_IF_ERROR(executor->SynchronousMemcpyH2D(
+ literal.untyped_data(), allocation.size(), &global));
+ }
+ }
+ }
+
+ module_handles_.emplace(executor,
+ se::ScopedModuleHandle(executor, module_handle));
+ return &module_globals_.emplace(executor, std::move(globals)).first->second;
+}
+
StatusOr<ScopedShapedBuffer> GpuExecutable::ExecuteOnStream(
const ServiceExecutableRunOptions* run_options,
tensorflow::gtl::ArraySlice<const ShapedBuffer*> arguments,
@@ -192,6 +242,10 @@ StatusOr<ScopedShapedBuffer> GpuExecutable::ExecuteOnStream(
}
BufferAllocations::Builder buffer_allocations_builder;
+ se::StreamExecutor* executor = run_options->stream()->parent();
+
+ TF_ASSIGN_OR_RETURN(auto* const globals, ResolveConstantGlobals(executor));
+
for (BufferAllocation::Index i = 0; i < assignment_->Allocations().size();
++i) {
const BufferAllocation& allocation = assignment_->GetAllocation(i);
@@ -213,8 +267,12 @@ StatusOr<ScopedShapedBuffer> GpuExecutable::ExecuteOnStream(
buffer_allocations_builder.RegisterBuffer(i, buffer);
}
+
+ if (allocation.is_constant()) {
+ buffer_allocations_builder.RegisterBuffer(i, FindOrDie(*globals, i));
+ }
}
- se::StreamExecutor* executor = run_options->stream()->parent();
+
TF_ASSIGN_OR_RETURN(
auto buffer_allocations,
buffer_allocations_builder.Build(
diff --git a/tensorflow/compiler/xla/service/gpu/gpu_executable.h b/tensorflow/compiler/xla/service/gpu/gpu_executable.h
index 80ec38c3ac..c7ce6d0acb 100644
--- a/tensorflow/compiler/xla/service/gpu/gpu_executable.h
+++ b/tensorflow/compiler/xla/service/gpu/gpu_executable.h
@@ -34,6 +34,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/types.h"
#include "tensorflow/core/lib/core/stringpiece.h"
#include "tensorflow/core/lib/gtl/array_slice.h"
+#include "tensorflow/core/lib/gtl/flatmap.h"
+#include "tensorflow/core/lib/gtl/optional.h"
#include "tensorflow/core/platform/macros.h"
#include "tensorflow/core/platform/stream_executor_no_cuda.h"
@@ -66,7 +68,7 @@ class GpuExecutable : public Executable {
}
// Returns the compiled PTX for the computation.
- tensorflow::StringPiece ptx() const { return ptx_; }
+ const string& ptx() const { return ptx_; }
// Returns the cubin (compiled PTX) stored in this GpuExecutable. May be
// empty, in which case compilation is left up to the GPU driver.
@@ -98,6 +100,15 @@ class GpuExecutable : public Executable {
// computation. Uses points-to analysis from buffer assignment.
const PointsToSet& GetRootPointsToSet() const;
+ using BufferAllocToDeviceMemoryMap =
+ tensorflow::gtl::FlatMap<BufferAllocation::Index, se::DeviceMemoryBase>;
+
+ // Loads the PTX or CUBIN for this executable into `executor` and resolves the
+ // globals corresponding to constant buffers. Returns a map mapping buffer
+ // allocation indices to GPU pointers.
+ StatusOr<const BufferAllocToDeviceMemoryMap*> ResolveConstantGlobals(
+ stream_executor::StreamExecutor* executor);
+
// The LLVM IR, in string format, of the unoptimized module generated for this
// GpuExecutable. We save a string instead of an llvm::Module* because leaving
// llvm::Module* in a singleton can cause the heap checker to emit false
@@ -126,6 +137,14 @@ class GpuExecutable : public Executable {
// memory for every output/temp buffers.
const std::unique_ptr<const BufferAssignment> assignment_;
+ // Cache of module handles and constant buffer allocation maps used by
+ // `ResolveConstantGlobals`.
+ tensorflow::mutex module_handle_mutex_;
+ std::map<stream_executor::StreamExecutor*, se::ScopedModuleHandle>
+ module_handles_ GUARDED_BY(module_handle_mutex_);
+ std::map<stream_executor::StreamExecutor*, BufferAllocToDeviceMemoryMap>
+ module_globals_ GUARDED_BY(module_handle_mutex_);
+
TF_DISALLOW_COPY_AND_ASSIGN(GpuExecutable);
};
diff --git a/tensorflow/compiler/xla/service/gpu/gpu_layout_assignment.cc b/tensorflow/compiler/xla/service/gpu/gpu_layout_assignment.cc
index 09ef62c87f..6ac5dfbcd5 100644
--- a/tensorflow/compiler/xla/service/gpu/gpu_layout_assignment.cc
+++ b/tensorflow/compiler/xla/service/gpu/gpu_layout_assignment.cc
@@ -31,20 +31,13 @@ limitations under the License.
namespace xla {
namespace gpu {
-using stream_executor::dnn::DataLayout;
-using stream_executor::dnn::FilterLayout;
-
-static bool IsVoltaOrLater(const se::StreamExecutor& stream_executor) {
- int major, minor;
- CHECK(stream_executor.GetDeviceDescription().cuda_compute_capability(&major,
- &minor));
- return major >= 7;
-}
+using se::dnn::DataLayout;
+using se::dnn::FilterLayout;
// Returns (input, filter, output) layouts.
static std::tuple<DataLayout, FilterLayout, DataLayout>
HeuristicLayoutAssignment(const HloInstruction* instr,
- stream_executor::StreamExecutor* stream_executor) {
+ se::StreamExecutor* stream_executor) {
// DataLayout and FilterLayout uses weird enum names. Translations:
// N <=> Batch or Output
// C <=> Depth or Input
@@ -52,31 +45,44 @@ HeuristicLayoutAssignment(const HloInstruction* instr,
// W <=> X
//
// Therefore kOutputInputYX and kBatchDepthYX mean NCHW.
+ //
+ // If you have trouble keeping these straight, consider that all that matters
+ // is the location of the channel dim: Is it major (NCHW), or minor (NHWC)?
+
+ constexpr auto kAllNCHW =
+ std::make_tuple(DataLayout::kBatchDepthYX, FilterLayout::kOutputInputYX,
+ DataLayout::kBatchDepthYX);
+ constexpr auto kAllNHWC =
+ std::make_tuple(DataLayout::kBatchYXDepth, FilterLayout::kOutputYXInput,
+ DataLayout::kBatchYXDepth);
- // As of today, our empirical evidence is that cudnn 7.0 is faster on V100 x
- // fp16 with the mostly-NHWC layout. The heuristic may change as cudnn version
- // changes, as well as the hardware updates.
+ // If we're not Volta or not fp16, the decision is easy: Use NCHW.
if (!(instr->operand(0)->shape().element_type() == xla::PrimitiveType::F16 &&
IsVoltaOrLater(*stream_executor))) {
- return std::make_tuple(DataLayout::kBatchDepthYX,
- FilterLayout::kOutputInputYX,
- DataLayout::kBatchDepthYX);
+ return kAllNCHW;
}
+
VLOG(2) << "Using heuristic to figure out layouts for " << instr->ToString();
- // For BackwardInput that has stride, full NHWC layouts run significantly
- // slower than (NHWC, NCHW, NCHW) or (NHWC, NCHW, NHWC).
+
+ // Empirically we've found with Volta and cudnn 7 that backward-input convs
+ // with stride are significantly faster with NCHW layouts.
//
- // TODO(timshen): more closely compare (NHWC, NCHW, NCHW) and (NHWC, NCHW,
- // NHWC).
+ // We could have used a mixed layout combination, e.g. (NHWC, NCHW, NCHW),
+ // which on paper gives good performance. However, there are two observations:
+ // * a mixed layout combination is more cuDNN-bug prone, based on empirical
+ // envidence.
+ // * we've also observed that for mixed layouts, cuDNN transposes data back
+ // and forth from a different layout combination. If we end up with
+ // transposes anyway, we prefer to have them in XLA, as they can be fused.
+ // TODO(timshen): Figure out the exact condition. This may be achieved by
+ // auto-tuning layouts offline.
if (instr->custom_call_target() == kCudnnConvBackwardInputCallTarget &&
window_util::HasStride(instr->window())) {
- return std::make_tuple(DataLayout::kBatchYXDepth,
- FilterLayout::kOutputInputYX,
- DataLayout::kBatchDepthYX);
+ return kAllNCHW;
}
- return std::make_tuple(DataLayout::kBatchYXDepth,
- FilterLayout::kOutputYXInput,
- DataLayout::kBatchYXDepth);
+
+ // For other Volta f16 convolutions, use NHWC.
+ return kAllNHWC;
}
// Adds layout constraints on the cudnn custom-call instruction. The layout
diff --git a/tensorflow/compiler/xla/service/gpu/hlo_execution_profiler.cc b/tensorflow/compiler/xla/service/gpu/hlo_execution_profiler.cc
index 19420e590d..1722676930 100644
--- a/tensorflow/compiler/xla/service/gpu/hlo_execution_profiler.cc
+++ b/tensorflow/compiler/xla/service/gpu/hlo_execution_profiler.cc
@@ -23,7 +23,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/service/hlo_computation.h"
#include "tensorflow/compiler/xla/service/hlo_execution_profile.h"
#include "tensorflow/compiler/xla/service/hlo_instruction.h"
-#include "tensorflow/compiler/xla/service/pool.h"
+#include "tensorflow/compiler/xla/service/stream_pool.h"
#include "tensorflow/core/platform/logging.h"
#include "tensorflow/core/platform/stream_executor_no_cuda.h"
#include "tensorflow/core/util/ptr_util.h"
@@ -37,10 +37,9 @@ void InitAndStartTimer(std::stack<std::unique_ptr<se::Timer>>* timers,
stream->InitTimer(timers->top().get()).ThenStartTimer(timers->top().get());
}
-uint64 GetCyclesTaken(
- std::stack<std::unique_ptr<se::Timer>>* timers,
- const std::vector<Pool<se::Stream>::SmartPtr>& sub_streams,
- se::Stream* stream, double clock_rate_ghz) {
+uint64 GetCyclesTaken(std::stack<std::unique_ptr<se::Timer>>* timers,
+ const std::vector<StreamPool::Ptr>& sub_streams,
+ se::Stream* stream, double clock_rate_ghz) {
CHECK_GT(timers->size(), 0);
stream->ThenWaitFor(&sub_streams);
stream->ThenStopTimer(timers->top().get());
@@ -53,7 +52,7 @@ uint64 GetCyclesTaken(
HloExecutionProfiler::HloExecutionProfiler(
bool do_profile, HloExecutionProfile* profile, se::Stream* stream,
- const std::vector<Pool<se::Stream>::SmartPtr>& sub_streams,
+ const std::vector<StreamPool::Ptr>& sub_streams,
const HloComputation* computation)
: do_profile_(do_profile),
profile_(profile),
diff --git a/tensorflow/compiler/xla/service/gpu/hlo_execution_profiler.h b/tensorflow/compiler/xla/service/gpu/hlo_execution_profiler.h
index 6654850bef..80cde75f2b 100644
--- a/tensorflow/compiler/xla/service/gpu/hlo_execution_profiler.h
+++ b/tensorflow/compiler/xla/service/gpu/hlo_execution_profiler.h
@@ -24,7 +24,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/service/hlo_computation.h"
#include "tensorflow/compiler/xla/service/hlo_execution_profile.h"
#include "tensorflow/compiler/xla/service/hlo_instruction.h"
-#include "tensorflow/compiler/xla/service/pool.h"
+#include "tensorflow/compiler/xla/service/stream_pool.h"
#include "tensorflow/core/platform/stream_executor_no_cuda.h"
namespace xla {
@@ -38,10 +38,10 @@ class ScopedInstructionProfiler;
class HloExecutionProfiler {
public:
// If profiling is enabled, start an execution timer running.
- explicit HloExecutionProfiler(
- bool do_profile, HloExecutionProfile* profile, se::Stream* stream,
- const std::vector<Pool<se::Stream>::SmartPtr>& sub_streams,
- const HloComputation* computation);
+ explicit HloExecutionProfiler(bool do_profile, HloExecutionProfile* profile,
+ se::Stream* stream,
+ const std::vector<StreamPool::Ptr>& sub_streams,
+ const HloComputation* computation);
// If profiling is enabled, sets the total cycle count on the profile from the
// execution timer.
@@ -72,7 +72,7 @@ class HloExecutionProfiler {
double clock_rate_ghz_;
HloExecutionProfile* profile_;
se::Stream* stream_;
- const std::vector<Pool<se::Stream>::SmartPtr>& sub_streams_;
+ const std::vector<StreamPool::Ptr>& sub_streams_;
const HloComputation* computation_;
std::stack<std::unique_ptr<se::Timer>> timers_;
// Contains the HLO instructions for which we are currently measuring the
diff --git a/tensorflow/compiler/xla/service/gpu/hlo_to_ir_bindings.cc b/tensorflow/compiler/xla/service/gpu/hlo_to_ir_bindings.cc
index 1b6315ec03..8c11cd0541 100644
--- a/tensorflow/compiler/xla/service/gpu/hlo_to_ir_bindings.cc
+++ b/tensorflow/compiler/xla/service/gpu/hlo_to_ir_bindings.cc
@@ -18,8 +18,10 @@ limitations under the License.
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Instructions.h"
+#include "tensorflow/compiler/xla/service/gpu/buffer_allocations.h"
#include "tensorflow/compiler/xla/service/gpu/ir_emission_utils.h"
#include "tensorflow/compiler/xla/service/hlo_opcode.h"
+#include "tensorflow/compiler/xla/service/llvm_ir/buffer_assignment_util.h"
#include "tensorflow/compiler/xla/service/llvm_ir/llvm_util.h"
#include "tensorflow/compiler/xla/service/llvm_ir/tuple_ops.h"
#include "tensorflow/core/lib/strings/str_util.h"
@@ -110,6 +112,12 @@ void HloToIrBindings::EmitBasePointersForHlos(
llvm_ir::ShapeToIrType(non_io_hlo->shape(), module_);
BindHloToIrValue(*non_io_hlo, b_->CreateAlloca(pointee_type),
index);
+ } else if (slice.allocation()->is_constant()) {
+ llvm::Value* global_for_constant =
+ module_->getGlobalVariable(llvm_ir::AsStringRef(
+ llvm_ir::ConstantBufferAllocationToGlobalName(
+ *slice.allocation())));
+ BindHloToIrValue(*non_io_hlo, global_for_constant);
} else {
const int64 offset = slice.offset();
CHECK_NE(nullptr, temp_buffer_base_);
@@ -135,6 +143,14 @@ llvm::Value* HloToIrBindings::EmitGetTupleElement(const HloInstruction* gte,
EmitGetTupleElement(gte->operand(0), base_ptr), b_, module_);
}
+// Returns true if `value` has a name that should not be changed.
+static bool HasMeaningfulName(llvm::Value* value) {
+ if (auto* global = llvm::dyn_cast<llvm::GlobalValue>(value)) {
+ return global->getLinkage() != llvm::GlobalValue::PrivateLinkage;
+ }
+ return false;
+}
+
llvm::Value* HloToIrBindings::GetTypedIrValue(const HloInstruction& hlo,
ShapeIndexView shape_index,
llvm::Value* ir_value) {
@@ -149,8 +165,13 @@ llvm::Value* HloToIrBindings::GetTypedIrValue(const HloInstruction& hlo,
} else {
typed_ir_value = b_->CreateBitCast(ir_value, pointee_type->getPointerTo());
}
- ir_value->setName(llvm_ir::AsStringRef(llvm_ir::IrName(&hlo, "raw")));
- typed_ir_value->setName(llvm_ir::AsStringRef(llvm_ir::IrName(&hlo, "typed")));
+ if (!HasMeaningfulName(ir_value)) {
+ ir_value->setName(llvm_ir::AsStringRef(llvm_ir::IrName(&hlo, "raw")));
+ }
+ if (!HasMeaningfulName(typed_ir_value)) {
+ typed_ir_value->setName(
+ llvm_ir::AsStringRef(llvm_ir::IrName(&hlo, "typed")));
+ }
return typed_ir_value;
}
diff --git a/tensorflow/compiler/xla/service/gpu/instruction_fusion.cc b/tensorflow/compiler/xla/service/gpu/instruction_fusion.cc
index 8abae43a5a..0f2c83aeb2 100644
--- a/tensorflow/compiler/xla/service/gpu/instruction_fusion.cc
+++ b/tensorflow/compiler/xla/service/gpu/instruction_fusion.cc
@@ -73,6 +73,67 @@ bool IsIEEEFloatingPointScalarConstant(const HloInstruction* constant) {
}
}
+// This function limits the maximum number of operands to a fusion.
+//
+// There's a cap on how many parameters we can pass to a CUDA kernel, but
+// exactly what that limit is is hazy, as it depends on (among other things) how
+// much GPU constant memory is in use for other purposes.
+//
+// Moreover, we don't even know at the point that we're running fusion how many
+// arguments the CUDA kernel for a fusion node will have: It depends on buffer
+// assignment, where we will decide which of the fusion's operands live in XLA's
+// big temp buffer versus in other allocations.
+//
+// As a heuristic, we simply cap the number of fusion operands plus outputs at
+// kMaxOperandsAndOutputsPerFusion. This puts an upper bound on the number of
+// parameters to the kernel, working around the correctness problem.
+//
+// This limit is also often good for performance. In a fusion with many
+// operands, each GPU thread likely has to do a lot of work, and so possibly
+// uses a lot of registers, thus limiting occupancy.
+/*static*/ bool GpuInstructionFusion::FusionWouldBeTooLarge(
+ const HloInstruction* a, const HloInstruction* b) {
+ // Compute the number of outputs of the (possibly multi-output) fusion node
+ // we're considering creating.
+ //
+ // This isn't precise; we may be off by one if
+ // - We're creating a multi-output fusion out of two non-MOFs. Creating a
+ // MOF adds a new buffer, namely, the tuple buffer.
+ // - We're merging two MOFs. In this case, we should count the tuple buffer
+ // only once.
+ // - WLOG there's an edge from `a` to `b` and `b` is the only consumer of
+ // `a`. In this case the result of `a` is not part of the output of the
+ // fusion.
+ //
+ // But because this is a heuristic and our limit
+ // kMaxOperandsAndOutputsPerFusion is a large value (so +/- 1 doesn't make a
+ // big difference), we ignore this small inaccuracy in favor of simplicity.
+ int64 num_output_buffers = ShapeUtil::SubshapeCount(a->shape()) +
+ ShapeUtil::SubshapeCount(b->shape());
+
+ // The new fusion will have no more operands and outputs than
+ // producer_operands + consumer_operands - 1 + num_output_buffers
+ // (minus one because we may be fusing a producer->consumer edge between `a`
+ // and `b`).
+ //
+ // This fact may be enough to let us avoid having to compute the true total
+ // number of operands, which can be expensive.
+ if (a->operand_count() + b->operand_count() - 1 + num_output_buffers <=
+ kMaxOperandsAndOutputsPerFusion) {
+ return false;
+ }
+
+ // Compute the precise number of operands to the new fusion.
+ tensorflow::gtl::FlatSet<const HloInstruction*> operands(
+ a->operands().begin(), a->operands().end());
+ operands.insert(b->operands().begin(), b->operands().end());
+ // If there's an edge between `a` and `b`, don't count it: We're fusing that
+ // producer -> consumer relationship.
+ operands.erase(a);
+ operands.erase(b);
+ return operands.size() + num_output_buffers > kMaxOperandsAndOutputsPerFusion;
+}
+
bool GpuInstructionFusion::ShouldFuse(HloInstruction* consumer,
int64 operand_index) {
HloInstruction* producer = consumer->mutable_operand(operand_index);
@@ -141,6 +202,7 @@ bool GpuInstructionFusion::ShouldFuse(HloInstruction* consumer,
IsIEEEFloatingPointScalarConstant(producer->operand(0)) &&
fused_parameter_users[0]->opcode() == HloOpcode::kMultiply;
}
+ return false;
}
// Other output fusions are not currently supported on GPUs.
@@ -188,48 +250,8 @@ bool GpuInstructionFusion::ShouldFuse(HloInstruction* consumer,
return false;
}
- // Limit the maximum number of operands to a fusion.
- //
- // There's a limit to how many parameters we can pass to a CUDA kernel, but
- // exactly what that limit is is hazy, as it depends on (among other things)
- // how much GPU constant memory is in use for other purposes.
- //
- // Moreover, we don't even know at this point how many arguments the CUDA
- // kernel for this fusion node will have: It depends on buffer assignment,
- // where we will decide which of the fusion's operands live in XLA's big temp
- // buffer versus in other allocations.
- //
- // As a heuristic, we simply cap the number of fusion operands at
- // kMaxOperandsPerFusion. This puts an upper bound on the number of
- // parameters to the kernel, working around the correctness problem.
- //
- // This limit is also often good for performance. In a fusion with many
- // operands, each GPU thread likely has to do a lot of work, and so possibly
- // uses a lot of registers, thus limiting occupancy.
- //
- // We put this check last because it's expensive to compute.
-
- // The new fusion will have no more operands than
- // producer_operands + consumer_operands - 1
- // (minus one because we're fusing the producer->consumer edge). This fact
- // may be enough to let us avoid having to compute the true total number of
- // operands, taking into account the fact that producer and consumer may share
- // operands.
- if (producer->operand_count() + consumer->operand_count() - 1 >
- kMaxOperandsPerFusion) {
- tensorflow::gtl::FlatSet<const HloInstruction*> producer_operands(
- producer->operands().begin(), producer->operands().end());
- int64 new_num_operands =
- producer->operand_count() +
- c_count_if(consumer->operands(), [&](const HloInstruction* operand) {
- return operand != producer && !producer_operands.count(operand);
- });
- if (new_num_operands > kMaxOperandsPerFusion) {
- return false;
- }
- }
-
- return true;
+ // We put this check last because it's potentially expensive.
+ return !FusionWouldBeTooLarge(consumer, producer);
}
bool GpuInstructionFusion::ShouldFuseIntoMultiOutput(HloInstruction* consumer,
diff --git a/tensorflow/compiler/xla/service/gpu/instruction_fusion.h b/tensorflow/compiler/xla/service/gpu/instruction_fusion.h
index 5ee1c004b6..c91f6343a6 100644
--- a/tensorflow/compiler/xla/service/gpu/instruction_fusion.h
+++ b/tensorflow/compiler/xla/service/gpu/instruction_fusion.h
@@ -27,6 +27,19 @@ class GpuInstructionFusion : public InstructionFusion {
explicit GpuInstructionFusion(bool may_duplicate)
: InstructionFusion(GpuInstructionFusion::IsExpensive, may_duplicate) {}
+ // Maximum number of operands plus outputs allowed on a single fusion node.
+ // Exposed publicly mainly for tests.
+ static constexpr int64 kMaxOperandsAndOutputsPerFusion = 64;
+
+ // Determines whether the combination of `a` and `b` into a (possibly
+ // multi-output) fusion would be "too large" -- i.e., have more operands and
+ // outputs than is allowed.
+ //
+ // `ShouldFuse` and `ShouldFuseIntoMultiOutput` call this; it's public so that
+ // other fusion passes (e.g. GPU multi-output fusion) can also call this.
+ static bool FusionWouldBeTooLarge(const HloInstruction* a,
+ const HloInstruction* b);
+
static bool IsExpensive(const HloInstruction& instruction);
bool ShouldFuse(HloInstruction* consumer, int64 operand_index) override;
@@ -36,10 +49,6 @@ class GpuInstructionFusion : public InstructionFusion {
HloInstruction::FusionKind ChooseKind(
const HloInstruction* producer, const HloInstruction* consumer) override;
-
- // Maximum number of operands allowed on a single fusion node. Exposed
- // publicly mainly for tests.
- static constexpr int64 kMaxOperandsPerFusion = 64;
};
} // namespace gpu
diff --git a/tensorflow/compiler/xla/service/gpu/instruction_fusion_test.cc b/tensorflow/compiler/xla/service/gpu/instruction_fusion_test.cc
index 229eb23f12..8d0522bd8f 100644
--- a/tensorflow/compiler/xla/service/gpu/instruction_fusion_test.cc
+++ b/tensorflow/compiler/xla/service/gpu/instruction_fusion_test.cc
@@ -609,7 +609,7 @@ TEST_F(InstructionFusionTest, FuseScalarConstant) {
// Check that we limit the number of operands to fusions we create.
TEST_F(InstructionFusionTest, AvoidsLargeFusion) {
constexpr int64 kNumParams = 200;
- ASSERT_GT(kNumParams, GpuInstructionFusion::kMaxOperandsPerFusion);
+ ASSERT_GT(kNumParams, GpuInstructionFusion::kMaxOperandsAndOutputsPerFusion);
// Compute p0 + p1 + ... + pN.
HloComputation::Builder b(TestName());
@@ -631,7 +631,7 @@ TEST_F(InstructionFusionTest, AvoidsLargeFusion) {
SCOPED_TRACE(module->ToString());
for (const HloInstruction* instr : computation->instructions()) {
EXPECT_LE(instr->operand_count(),
- GpuInstructionFusion::kMaxOperandsPerFusion)
+ GpuInstructionFusion::kMaxOperandsAndOutputsPerFusion)
<< instr->ToString();
}
}
diff --git a/tensorflow/compiler/xla/service/gpu/ir_emission_utils.cc b/tensorflow/compiler/xla/service/gpu/ir_emission_utils.cc
index 2799baab41..6352b330d1 100644
--- a/tensorflow/compiler/xla/service/gpu/ir_emission_utils.cc
+++ b/tensorflow/compiler/xla/service/gpu/ir_emission_utils.cc
@@ -81,11 +81,6 @@ bool DotImplementedAsGemm(const HloInstruction& dot) {
} // namespace
bool ImplementedAsGemm(const HloInstruction& hlo) {
- // We can only do this if the HLO is unnested.
- if (hlo.parent() != hlo.GetModule()->entry_computation()) {
- return false;
- }
-
// For certain types of Dot, we can call pre-canned BLAS gemm.
if (hlo.opcode() == HloOpcode::kDot) {
return DotImplementedAsGemm(hlo);
diff --git a/tensorflow/compiler/xla/service/gpu/ir_emission_utils.h b/tensorflow/compiler/xla/service/gpu/ir_emission_utils.h
index 9bb4c42b15..5d23a3d018 100644
--- a/tensorflow/compiler/xla/service/gpu/ir_emission_utils.h
+++ b/tensorflow/compiler/xla/service/gpu/ir_emission_utils.h
@@ -31,6 +31,12 @@ namespace gpu {
constexpr int64 kWarpSize = 32;
// Returns true if `hlo` will be implemented as a call to BLAS gemm.
+//
+// Precondition: `hlo` is in an "unnested context", meaning, it lives within the
+// entry computation, within the either of a while loop's subcomputations,
+// within any of a conditional's subcomputations, etc., but *does not* live
+// within a reduce subcomputation, a map subcomputation, a fusion
+// subcomputation, etc. It's OK if `hlo` *is* a fusion.
bool ImplementedAsGemm(const HloInstruction& hlo);
// A call to cuDNN for batch normalization is represented as CustomCall HLO with
diff --git a/tensorflow/compiler/xla/service/gpu/ir_emitter.cc b/tensorflow/compiler/xla/service/gpu/ir_emitter.cc
index f95541cba4..1295e83c0c 100644
--- a/tensorflow/compiler/xla/service/gpu/ir_emitter.cc
+++ b/tensorflow/compiler/xla/service/gpu/ir_emitter.cc
@@ -81,19 +81,6 @@ Status IrEmitter::DefaultAction(HloInstruction* hlo) {
}
Status IrEmitter::HandleConstant(HloInstruction* constant) {
- const Literal& literal = constant->literal();
- llvm::Constant* initializer =
- llvm_ir::ConvertLiteralToIrConstant(literal, module_);
- llvm::GlobalVariable* global_for_const = new llvm::GlobalVariable(
- *module_, initializer->getType(),
- /*isConstant=*/true, llvm::GlobalValue::PrivateLinkage, initializer,
- /*Name=*/"");
- VLOG(2) << "HandleConstant: " << constant->ToString() << std::endl
- << " emitted_value: " << llvm_ir::DumpToString(*global_for_const)
- << std::endl
- << " its type: "
- << llvm_ir::DumpToString(*global_for_const->getType());
- bindings_.BindHloToIrValue(*constant, global_for_const);
return Status::OK();
}
@@ -716,23 +703,6 @@ Status IrEmitter::HandleOutfeed(HloInstruction*) {
return Unimplemented("Outfeed is not supported on GPU.");
}
-Status IrEmitter::HandleRng(HloInstruction* random) {
- ElementalIrEmitter::HloToElementGeneratorMap operand_to_generator;
- for (const HloInstruction* operand : random->operands()) {
- operand_to_generator[operand] = [=](const llvm_ir::IrArray::Index& index) {
- return GetIrArray(*operand, *random).EmitReadArrayElement(index, &b_);
- };
- }
- // Emits a single-threaded loop because the loop body generated by the element
- // generator for Rng can't be parallelized (b/32333178).
- return llvm_ir::LoopEmitter(
- GpuElementalIrEmitter(hlo_module_config_, module_, &b_,
- GetNestedComputer())
- .MakeElementGenerator(random, operand_to_generator),
- GetIrArray(*random, *random), &b_)
- .EmitLoop(IrName(random));
-}
-
Status IrEmitter::HandleBatchNormInference(HloInstruction*) {
return Unimplemented(
"The GPU backend does not implement BatchNormInference directly. It "
diff --git a/tensorflow/compiler/xla/service/gpu/ir_emitter.h b/tensorflow/compiler/xla/service/gpu/ir_emitter.h
index e89967a378..80e2a203ac 100644
--- a/tensorflow/compiler/xla/service/gpu/ir_emitter.h
+++ b/tensorflow/compiler/xla/service/gpu/ir_emitter.h
@@ -91,7 +91,6 @@ class IrEmitter : public DfsHloVisitorWithDefault {
Status HandleFusion(HloInstruction* fusion) override;
Status HandleCall(HloInstruction* call) override;
Status HandleCustomCall(HloInstruction* custom_call) override;
- Status HandleRng(HloInstruction* random) override;
Status HandleBatchNormInference(HloInstruction* batch_norm) override;
Status HandleBatchNormTraining(HloInstruction* batch_norm) override;
Status HandleBatchNormGrad(HloInstruction* batch_norm) override;
diff --git a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc
index 1f31a7f36b..874c7cfb8a 100644
--- a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc
+++ b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.cc
@@ -33,6 +33,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/service/buffer_assignment.h"
#include "tensorflow/compiler/xla/service/dfs_hlo_visitor.h"
#include "tensorflow/compiler/xla/service/gpu/backend_configs.pb.h"
+#include "tensorflow/compiler/xla/service/gpu/buffer_allocations.h"
#include "tensorflow/compiler/xla/service/gpu/conditional_thunk.h"
#include "tensorflow/compiler/xla/service/gpu/convolution_thunk.h"
#include "tensorflow/compiler/xla/service/gpu/copy_thunk.h"
@@ -59,6 +60,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/service/hlo_computation.h"
#include "tensorflow/compiler/xla/service/hlo_instruction.h"
#include "tensorflow/compiler/xla/service/hlo_opcode.h"
+#include "tensorflow/compiler/xla/service/llvm_ir/buffer_assignment_util.h"
#include "tensorflow/compiler/xla/service/llvm_ir/dynamic_update_slice_util.h"
#include "tensorflow/compiler/xla/service/llvm_ir/fused_ir_emitter.h"
#include "tensorflow/compiler/xla/service/llvm_ir/kernel_support_library.h"
@@ -75,6 +77,7 @@ limitations under the License.
#include "tensorflow/core/lib/core/bits.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/lib/gtl/array_slice.h"
+#include "tensorflow/core/lib/gtl/optional.h"
#include "tensorflow/core/platform/logging.h"
namespace xla {
@@ -230,11 +233,20 @@ llvm::Function* IrEmitterUnnested::BuildKernelPrototype(
++arg_it;
kernel->addDereferenceableAttr(arg_no + 1, alloc->size());
+
+ const int64 alignment = [&] {
+ if (alloc->is_entry_computation_parameter()) {
+ return kEntryParameterAlignBytes;
+ } else if (alloc->is_constant()) {
+ return kConstantBufferAlignBytes;
+ } else {
+ return kXlaAllocatedBufferAlignBytes;
+ }
+ }();
+
kernel->addParamAttr(
- arg_no, llvm::Attribute::get(context, llvm::Attribute::Alignment,
- alloc->is_entry_computation_parameter()
- ? kEntryParameterAlignBytes
- : kXlaAllocatedBufferAlignBytes));
+ arg_no,
+ llvm::Attribute::get(context, llvm::Attribute::Alignment, alignment));
if (alloc->IsPreallocatedTempBuffer()) {
fn_arg->setName("temp_buf");
@@ -1762,6 +1774,8 @@ Status IrEmitterUnnested::HandleTuple(HloInstruction* tuple) {
.GetUniqueTopLevelSlice(tuple_element)
.ok();
});
+ // TODO(b/111689850): This logic isn't quite correct.
+ //
// Tuples (especially tuples that are the final result of a computation) can
// be so huge that if we were to emit a kernel that took each tuple element as
// a parameter, we would exceed the max allowable number of parameters to a
@@ -1769,9 +1783,9 @@ Status IrEmitterUnnested::HandleTuple(HloInstruction* tuple) {
// buffer, we collect their buffer addresses in a host array, and then copy
// that array to the tuple's buffer.
//
- // Some tuple elements (e.g. const or bitcast of const) might not have a
- // buffer -- their contents are stored in code. In that case, we fall back to
- // emitting kernels which have access to their buffer addresses in code.
+ // Some tuple elements might not have an unambiguous buffer (like the result
+ // of a select-tuple). In that case, we fall back to emitting kernels which
+ // have access to their buffer addresses in code.
if (all_tuple_elements_have_buffer) {
std::vector<BufferAllocation::Slice> tuple_element_buffers;
for (const HloInstruction* tuple_element : tuple->operands()) {
@@ -2006,10 +2020,44 @@ Status IrEmitterUnnested::HandleWhile(HloInstruction* xla_while) {
return Status::OK();
}
-Status IrEmitterUnnested::HandleRng(HloInstruction* random) {
- thunk_sequence_->push_back(
- BuildKernelThunk(random, /*implements_whole_instruction=*/true));
- return IrEmitter::HandleRng(random);
+Status IrEmitterUnnested::HandleRng(HloInstruction* rng) {
+ // Build the kernel to generate the random numbers.
+ //
+ // Unroll the kernel so that the duplicated computation that calculates the
+ // 128 bit sample can be optimized away by LLVM.
+ thunk_sequence_->emplace_back(
+ BuildKernelThunk(rng, /*implements_whole_instruction=*/false,
+ ComputeMaxUnrollFactor(rng)));
+ ElementalIrEmitter::HloToElementGeneratorMap operand_to_generator;
+ for (const HloInstruction* operand : rng->operands()) {
+ operand_to_generator[operand] = [=](const llvm_ir::IrArray::Index& index) {
+ return GetIrArray(*operand, *rng).EmitReadArrayElement(index, &b_);
+ };
+ }
+ TF_RETURN_IF_ERROR(EmitTargetElementLoop(
+ *rng, GpuElementalIrEmitter(hlo_module_config_, module_, &b_,
+ GetNestedComputer())
+ .MakeElementGenerator(rng, operand_to_generator)));
+ std::unique_ptr<Thunk> rng_thunk = std::move(thunk_sequence_->back());
+ thunk_sequence_->pop_back();
+
+ // Emit a kernel to increment the global state for Philox RNG algorithm.
+ thunk_sequence_->emplace_back(
+ BuildKernelThunk(rng, /*implements_whole_instruction=*/false));
+ llvm_ir::IncrementVariableForPhiloxRngState(1, module_, &b_);
+ std::unique_ptr<Thunk> increment_seed_thunk =
+ std::move(thunk_sequence_->back());
+ thunk_sequence_->pop_back();
+
+ // Build the SequentialThunk for the RNG hlo.
+ std::vector<std::unique_ptr<Thunk>> thunks;
+ thunks.reserve(2);
+ thunks.push_back(std::move(rng_thunk));
+ thunks.push_back(std::move(increment_seed_thunk));
+ thunk_sequence_->emplace_back(
+ MakeUnique<SequentialThunk>(std::move(thunks), rng));
+
+ return Status::OK();
}
Status IrEmitterUnnested::HandleSelect(HloInstruction* select) {
@@ -2020,28 +2068,34 @@ Status IrEmitterUnnested::HandleSelect(HloInstruction* select) {
Status IrEmitterUnnested::HandleSort(HloInstruction* sort) {
std::vector<std::unique_ptr<Thunk>> thunks;
+ auto keys = sort->operand(0);
auto values = sort->operand_count() > 1 ? sort->operand(1) : nullptr;
+ ShapeIndex keys_shape_index({});
+ ShapeIndex values_shape_index({});
if (values != nullptr) {
- // TODO(b/26783907): Also sort the values by their corresponding key.
- return Unimplemented("Key/Value Sort is not implemented on GPU");
+ keys_shape_index = ShapeIndex({0});
+ values_shape_index = ShapeIndex({1});
}
+ auto keys_destination = GetAllocationSlice(*sort, keys_shape_index);
+ auto values_destination = GetAllocationSlice(*sort, values_shape_index);
- // First copy the operand to the output, so that we can sort in-place.
- // TODO(b/26783907): Share buffer of output and operand when it is possible.
- if (sort->operand(0)->IsConstant()) {
- thunks.push_back(MakeUnique<HostToDeviceCopyThunk>(
- /*source_address=*/sort->operand(0)->literal().untyped_data(),
- /*destination_buffer=*/GetAllocationSlice(*sort),
- /*mem_size=*/ShapeUtil::ByteSizeOf(sort->shape()), sort));
- } else {
+ if (keys_destination != GetAllocationSlice(*keys)) {
+ thunks.push_back(MakeUnique<DeviceToDeviceCopyThunk>(
+ /*source_address=*/GetAllocationSlice(*keys),
+ /*destination_buffer=*/keys_destination,
+ /*mem_size=*/ShapeUtil::ByteSizeOf(keys->shape()), nullptr));
+ }
+ if (values != nullptr && values_destination != GetAllocationSlice(*values)) {
+ // TODO(b/26783907): Figure out why we never seem to share buffers for
+ // key/value sort.
thunks.push_back(MakeUnique<DeviceToDeviceCopyThunk>(
- /*source_address=*/GetAllocationSlice(*sort->operand(0)),
- /*destination_buffer=*/GetAllocationSlice(*sort),
- /*mem_size=*/ShapeUtil::ByteSizeOf(sort->shape()), sort));
+ /*source_address=*/GetAllocationSlice(*values),
+ /*destination_buffer=*/values_destination,
+ /*mem_size=*/ShapeUtil::ByteSizeOf(values->shape()), nullptr));
}
int64 dimension_to_sort = sort->dimensions(0);
- int64 dimension_to_sort_bound = sort->shape().dimensions(dimension_to_sort);
+ int64 dimension_to_sort_bound = keys->shape().dimensions(dimension_to_sort);
int64 num_stages = tensorflow::Log2Ceiling(dimension_to_sort_bound);
auto index_type = b_.getInt64Ty();
@@ -2065,7 +2119,7 @@ Status IrEmitterUnnested::HandleSort(HloInstruction* sort) {
thunks.push_back(
BuildKernelThunk(sort, /*implements_whole_instruction=*/false));
LaunchDimensions launch_dimensions = CalculateLaunchDimensions(
- sort->shape(), ir_emitter_context_->device_description());
+ keys->shape(), ir_emitter_context_->device_description());
UpdateLaunchDimensions(launch_dimensions, thunks.back().get(),
ir_emitter_context_->llvm_module());
@@ -2077,8 +2131,11 @@ Status IrEmitterUnnested::HandleSort(HloInstruction* sort) {
}
TF_RETURN_IF_ERROR(llvm_ir::EmitSortInPlace(
- dimension_to_sort, GetIrArray(*sort, *sort), IrName(sort), xor_mask,
- &b_, &launch_dimensions));
+ dimension_to_sort, GetIrArray(*sort, *sort, keys_shape_index),
+ values != nullptr ? tensorflow::gtl::make_optional<IrArray>(
+ GetIrArray(*sort, *sort, values_shape_index))
+ : tensorflow::gtl::nullopt,
+ IrName(sort), xor_mask, &b_, &launch_dimensions));
}
}
@@ -2240,11 +2297,6 @@ GetHloBufferSlices(const HloInstruction* hlo,
// Adds entries for all subshapes of instr to `slices`.
auto add_slices_for = [&](const HloInstruction* instr) {
- // GPU constants don't have buffers; don't bother looking for one.
- if (instr->IsConstant()) {
- return;
- }
-
ShapeUtil::ForEachSubshape(
instr->shape(), [&](const Shape& /*shape*/, const ShapeIndex& index) {
if (slices.count({instr, index})) {
@@ -2306,21 +2358,25 @@ std::unique_ptr<KernelThunk> IrEmitterUnnested::BuildKernelThunk(
// We'll pass a pointer to each of the elements of `buffers` to our kernel, in
// this order.
- std::vector<const BufferAllocation*> buffers(buffers_needed.begin(),
- buffers_needed.end());
- std::sort(buffers.begin(), buffers.end(),
+ std::vector<const BufferAllocation*> non_constant_buffers;
+ c_copy_if(buffers_needed, std::back_inserter(non_constant_buffers),
+ [](const BufferAllocation* allocation) {
+ return !allocation->is_constant();
+ });
+
+ std::sort(non_constant_buffers.begin(), non_constant_buffers.end(),
[](const BufferAllocation* a, const BufferAllocation* b) {
return a->index() < b->index();
});
- llvm::Function* kernel = BuildKernelPrototype(*inst, buffers);
+ llvm::Function* kernel = BuildKernelPrototype(*inst, non_constant_buffers);
// Build a map from a BufferAllocation to the corresponding argument in our
// kernel.
std::unordered_map<const BufferAllocation*, llvm::Value*> kernel_args;
{
auto arg_it = kernel->arg_begin();
- auto buffers_it = buffers.begin();
+ auto buffers_it = non_constant_buffers.begin();
for (; arg_it != kernel->arg_end(); ++arg_it, ++buffers_it) {
kernel_args[*buffers_it] = arg_it;
}
@@ -2338,8 +2394,16 @@ std::unique_ptr<KernelThunk> IrEmitterUnnested::BuildKernelThunk(
<< " is found in slice " << slice.ToString() << " at GTE index "
<< gte_index.ToString();
- llvm::Value* loc = b_.CreateInBoundsGEP(kernel_args.at(slice.allocation()),
- {b_.getInt64(slice.offset())});
+ llvm::Value* loc;
+ if (slice.allocation()->is_constant()) {
+ loc = ir_emitter_context_->llvm_module()->getGlobalVariable(
+ llvm_ir::AsStringRef(llvm_ir::ConstantBufferAllocationToGlobalName(
+ *slice.allocation())));
+ CHECK_NE(loc, nullptr);
+ } else {
+ loc = b_.CreateInBoundsGEP(kernel_args.at(slice.allocation()),
+ {b_.getInt64(slice.offset())});
+ }
// If gte_index is nonempty, we have to dereference `loc` to get to the
// value we're ultimately interested in.
@@ -2362,9 +2426,9 @@ std::unique_ptr<KernelThunk> IrEmitterUnnested::BuildKernelThunk(
llvm::ConstantPointerNull::get(b_.getInt8PtrTy()));
}
- return MakeUnique<KernelThunk>(buffers, llvm_ir::AsString(kernel->getName()),
- implements_whole_instruction ? inst : nullptr,
- unroll_factor);
+ return MakeUnique<KernelThunk>(
+ non_constant_buffers, llvm_ir::AsString(kernel->getName()),
+ implements_whole_instruction ? inst : nullptr, unroll_factor);
}
std::unique_ptr<Thunk> IrEmitterUnnested::BuildHostToDeviceCopyThunk(
@@ -2601,7 +2665,17 @@ StatusOr<std::unique_ptr<Thunk>> IrEmitterUnnested::BuildInitializerThunk(
// If the init_value was fused into this reduce we have to generate it first.
if (fused && init_value_operand->opcode() != HloOpcode::kParameter) {
CHECK_EQ(HloOpcode::kConstant, init_value_operand->opcode());
- TF_RETURN_IF_ERROR(HandleConstant(const_cast<HloInstruction*>(init_value)));
+
+ const Literal& literal = init_value_operand->literal();
+ llvm::Constant* initializer =
+ llvm_ir::ConvertLiteralToIrConstant(literal, module_);
+
+ llvm::GlobalVariable* global_for_const = new llvm::GlobalVariable(
+ *module_, initializer->getType(),
+ /*isConstant=*/true, llvm::GlobalValue::PrivateLinkage, initializer,
+ /*Name=*/"");
+ global_for_const->setAlignment(kConstantBufferAlignBytes);
+ bindings_.BindHloToIrValue(*init_value_operand, global_for_const);
}
TF_RETURN_IF_ERROR(ParallelLoopEmitter(
[=](const IrArray::Index& index) {
@@ -2719,13 +2793,13 @@ std::unique_ptr<Thunk> IrEmitterUnnested::BuildWhileThunk(
HloComputation* condition = hlo->while_condition();
IrEmitterUnnested ir_emitter_condition(hlo_module_config_, condition,
ir_emitter_context_);
- TF_CHECK_OK(condition->root_instruction()->Accept(&ir_emitter_condition));
+ TF_CHECK_OK(condition->Accept(&ir_emitter_condition));
// Generate thunk sequence for while 'body'.
HloComputation* body = hlo->while_body();
IrEmitterUnnested ir_emitter_body(hlo_module_config_, body,
ir_emitter_context_);
- TF_CHECK_OK(body->root_instruction()->Accept(&ir_emitter_body));
+ TF_CHECK_OK(body->Accept(&ir_emitter_body));
return MakeUnique<WhileThunk>(
GetAllocationSlice(*condition->root_instruction()), // cond result
@@ -2743,7 +2817,7 @@ std::unique_ptr<Thunk> IrEmitterUnnested::BuildForThunk(
HloComputation* body = hlo->while_body();
IrEmitterUnnested ir_emitter_body(hlo_module_config_, body,
ir_emitter_context_);
- TF_CHECK_OK(body->root_instruction()->Accept(&ir_emitter_body));
+ TF_CHECK_OK(body->Accept(&ir_emitter_body));
return MakeUnique<ForThunk>(loop_limit,
ir_emitter_body.ConsumeThunkSequence(), hlo);
@@ -2759,12 +2833,12 @@ std::unique_ptr<Thunk> IrEmitterUnnested::BuildConditionalThunk(
HloComputation* true_computation = hlo->true_computation();
IrEmitterUnnested ir_emitter_true(hlo_module_config_, true_computation,
ir_emitter_context_);
- TF_CHECK_OK(true_computation->root_instruction()->Accept(&ir_emitter_true));
+ TF_CHECK_OK(true_computation->Accept(&ir_emitter_true));
HloComputation* false_computation = hlo->false_computation();
IrEmitterUnnested ir_emitter_false(hlo_module_config_, false_computation,
ir_emitter_context_);
- TF_CHECK_OK(false_computation->root_instruction()->Accept(&ir_emitter_false));
+ TF_CHECK_OK(false_computation->Accept(&ir_emitter_false));
return MakeUnique<ConditionalThunk>(
GetAllocationSlice(*hlo->operand(0)),
@@ -3333,5 +3407,47 @@ bool IrEmitterUnnested::CheckAndEmitHloWithTile021(HloInstruction* hlo) {
return true;
}
+Status IrEmitterUnnested::EmitConstantGlobals() {
+ for (const BufferAllocation& allocation :
+ ir_emitter_context_->buffer_assignment().Allocations()) {
+ if (!allocation.is_constant()) {
+ continue;
+ }
+
+ const Literal& literal = llvm_ir::LiteralForConstantAllocation(allocation);
+ const bool should_emit_initializer = ShouldEmitLiteralInLlvmIr(literal);
+ llvm::ArrayType* global_type =
+ llvm::ArrayType::get(b_.getInt8Ty(), allocation.size());
+ llvm::Constant* initializer =
+ should_emit_initializer
+ ? llvm_ir::ConvertLiteralToIrConstant(literal, module_)
+ : llvm::ConstantAggregateZero::get(global_type);
+ if (should_emit_initializer) {
+ VLOG(3) << "Emitted initializer for constant with shape "
+ << ShapeUtil::HumanString(literal.shape());
+ }
+
+ // These globals will be looked up by name by GpuExecutable so we need to
+ // give them an external linkage. Not all of their uses are visible in the
+ // LLVM IR (e.g. TupleThunk) so we can't give then a linkage that merely
+ // preserves their names (like available_externally), we also need to ensure
+ // that they stick around even if they're "unused".
+ //
+ // We may have to be more more clever here in the future if we notice that
+ // we're keeping around too many globals because of their linkage.
+ llvm::GlobalVariable* global_for_const = new llvm::GlobalVariable(
+ global_type, /*isConstant=*/should_emit_initializer,
+ llvm::GlobalValue::ExternalLinkage,
+ /*Initializer=*/initializer,
+ llvm_ir::AsStringRef(
+ llvm_ir::ConstantBufferAllocationToGlobalName(allocation)));
+ global_for_const->setAlignment(kConstantBufferAlignBytes);
+ ir_emitter_context_->llvm_module()->getGlobalList().push_back(
+ global_for_const);
+ }
+
+ return Status::OK();
+}
+
} // namespace gpu
} // namespace xla
diff --git a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h
index 616d8a2206..5254419907 100644
--- a/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h
+++ b/tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h
@@ -92,6 +92,9 @@ class IrEmitterUnnested : public IrEmitter {
const HloInstruction& hlo, const llvm_ir::ElementGenerator& body_emitter,
KernelThunk* thunk);
+ // Emits LLVM global variables corresponding to constant instructions.
+ Status EmitConstantGlobals();
+
private:
// Builds the appropriate thunk for the instruction hlo and returns the owning
// pointer to it. The caller needs to make sure `inst` outlives the lifetime
diff --git a/tensorflow/compiler/xla/service/gpu/multi_output_fusion.cc b/tensorflow/compiler/xla/service/gpu/multi_output_fusion.cc
index f95fbb01f9..c67dcbce77 100644
--- a/tensorflow/compiler/xla/service/gpu/multi_output_fusion.cc
+++ b/tensorflow/compiler/xla/service/gpu/multi_output_fusion.cc
@@ -24,6 +24,7 @@ limitations under the License.
#include <utility>
#include "tensorflow/compiler/xla/layout_util.h"
+#include "tensorflow/compiler/xla/service/gpu/instruction_fusion.h"
#include "tensorflow/compiler/xla/service/gpu/ir_emission_utils.h"
#include "tensorflow/compiler/xla/service/hlo_instruction.h"
#include "tensorflow/compiler/xla/service/hlo_opcode.h"
@@ -112,7 +113,7 @@ bool IsInputFusibleReduction(HloInstruction* instr) {
// of input parameters differ. In such situtations it is beneficial not to fuse.
// We consider input params with maximum rank only. Params with smaller ranks
// will be broadcasted and have not been observed to cause data locality issues.
-// TODO(b/110927656): Improve reduce emitters to remove this limitation.
+// TODO(b/111977086): Improve reduce emitters to remove this limitation.
bool ReduceFriendlyInputLayouts(HloInstruction* instr) {
int64 max_rank = 0;
const Layout* max_rank_layout;
@@ -163,16 +164,22 @@ bool GpuMultiOutputFusion::LegalToFuse(HloInstruction* instr1,
if (!MultiOutputFusion::LegalToFuse(instr1, instr2)) {
return false;
}
+
// If we're fusing fusions only do it if the fusion kind matches. Loop fusions
// merge into bigger loop fusions and input (reduce) fusions become fusions
// with multiple reduce outputs. We could fuse reduce and loop fusions
// together too (the result being an input fusion) if we find cases where this
// improves things.
CHECK(instr1->opcode() == HloOpcode::kFusion);
- if (instr2->opcode() == HloOpcode::kFusion) {
- return instr1->fusion_kind() == instr2->fusion_kind();
+ if ((instr2->opcode() == HloOpcode::kFusion &&
+ instr1->fusion_kind() != instr2->fusion_kind()) ||
+ (instr2->opcode() != HloOpcode::kFusion &&
+ instr1->fusion_kind() == HloInstruction::FusionKind::kLoop)) {
+ return false;
}
- return instr1->fusion_kind() != HloInstruction::FusionKind::kLoop;
+
+ // Do this check last, as it may be expensive.
+ return !GpuInstructionFusion::FusionWouldBeTooLarge(instr1, instr2);
}
bool GpuMultiOutputFusion::DoProducerConsumerMultiOutputFusion() {
diff --git a/tensorflow/compiler/xla/service/gpu/multi_output_fusion_test.cc b/tensorflow/compiler/xla/service/gpu/multi_output_fusion_test.cc
index 451e49f23a..ec4234b8d9 100644
--- a/tensorflow/compiler/xla/service/gpu/multi_output_fusion_test.cc
+++ b/tensorflow/compiler/xla/service/gpu/multi_output_fusion_test.cc
@@ -15,6 +15,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/service/gpu/multi_output_fusion.h"
+#include "tensorflow/compiler/xla/service/gpu/instruction_fusion.h"
#include "tensorflow/compiler/xla/service/hlo_matchers.h"
#include "tensorflow/compiler/xla/service/hlo_parser.h"
#include "tensorflow/compiler/xla/status_macros.h"
@@ -419,5 +420,58 @@ TEST_F(MultiOutputFusionTest,
ASSERT_FALSE(GpuMultiOutputFusion().Run(module.get()).ValueOrDie());
}
+// Check that we limit the number of operands to fusions we create.
+TEST_F(MultiOutputFusionTest, AvoidsLargeFusion) {
+ constexpr int64 kNumParams = 200;
+ ASSERT_GT(kNumParams, GpuInstructionFusion::kMaxOperandsAndOutputsPerFusion);
+
+ // Compute
+ // p0 * p1,
+ // p0 * p1 + p1 * p2
+ // p0 * p1 + p1 * p2 + p2 * p3
+ // ...
+ // where each of the (pi * pj)'s is represented as a fusion node so that
+ // multi-output fusion will pay attention to it.
+ auto module = CreateNewModule();
+ HloComputation::Builder b(TestName());
+ Shape shape = ShapeUtil::MakeShape(F32, {10, 100});
+
+ std::vector<HloInstruction*> params;
+ for (int64 i = 0; i < kNumParams; ++i) {
+ params.push_back(
+ b.AddInstruction(HloInstruction::CreateParameter(i, shape, "p")));
+ }
+
+ // Creates a fusion node that calculates x*y.
+ auto make_fusion = [&](HloInstruction* x, HloInstruction* y) {
+ HloComputation::Builder sub_builder("subcomp");
+ auto* p0 = sub_builder.AddInstruction(
+ HloInstruction::CreateParameter(0, shape, "p"));
+ auto* p1 = sub_builder.AddInstruction(
+ HloInstruction::CreateParameter(1, shape, "p"));
+ sub_builder.AddInstruction(
+ HloInstruction::CreateBinary(shape, HloOpcode::kMultiply, p0, p1));
+ HloComputation* subcomp =
+ module->AddEmbeddedComputation(sub_builder.Build());
+ return HloInstruction::CreateFusion(
+ shape, HloInstruction::FusionKind::kLoop, {x, y}, subcomp);
+ };
+
+ auto* sum = b.AddInstruction(make_fusion(params[0], params[1]));
+ for (int64 i = 2; i < kNumParams; ++i) {
+ sum = b.AddInstruction(HloInstruction::CreateBinary(
+ shape, HloOpcode::kAdd, sum,
+ b.AddInstruction(make_fusion(params[i - 1], params[i]))));
+ }
+ auto computation = module->AddEntryComputation(b.Build());
+ EXPECT_TRUE(GpuMultiOutputFusion().Run(module.get()).ValueOrDie());
+ SCOPED_TRACE(module->ToString());
+ for (const HloInstruction* instr : computation->instructions()) {
+ EXPECT_LE(instr->operand_count() + ShapeUtil::SubshapeCount(instr->shape()),
+ GpuInstructionFusion::kMaxOperandsAndOutputsPerFusion)
+ << instr->ToString();
+ }
+}
+
} // namespace gpu
} // namespace xla
diff --git a/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc b/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc
index 2eefadebcd..7a683ede54 100644
--- a/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc
+++ b/tensorflow/compiler/xla/service/gpu/nvptx_compiler.cc
@@ -52,9 +52,11 @@ limitations under the License.
#include "tensorflow/compiler/xla/service/gpu/ir_emitter_unnested.h"
#include "tensorflow/compiler/xla/service/gpu/llvm_gpu_backend/nvptx_backend_lib.h"
#include "tensorflow/compiler/xla/service/gpu/multi_output_fusion.h"
+#include "tensorflow/compiler/xla/service/gpu/pad_for_tensor_cores.h"
#include "tensorflow/compiler/xla/service/gpu/pad_insertion.h"
#include "tensorflow/compiler/xla/service/gpu/partition_assignment.h"
#include "tensorflow/compiler/xla/service/gpu/stream_assignment.h"
+#include "tensorflow/compiler/xla/service/gpu/stream_executor_util.h"
#include "tensorflow/compiler/xla/service/gpu/thunk_schedule.h"
#include "tensorflow/compiler/xla/service/hlo.pb.h"
#include "tensorflow/compiler/xla/service/hlo_computation.h"
@@ -199,6 +201,12 @@ Status OptimizeHloModule(HloModule* hlo_module, se::StreamExecutor* stream_exec,
pipeline.AddInvariantChecker<HloVerifier>();
pipeline.AddPass<CudnnConvolutionRewriter>();
pipeline.AddPass<PadInsertion>();
+ if (IsVoltaOrLater(*stream_exec)) {
+ pipeline.AddPass<PadForTensorCores>();
+ // PadForTensorCores leaves behind unnecessary tuple/get-tuple-element
+ // pairs that TupleSimplifier fixes.
+ pipeline.AddPass<TupleSimplifier>();
+ }
TF_RETURN_IF_ERROR(pipeline.Run(hlo_module).status());
}
@@ -540,11 +548,13 @@ StatusOr<std::unique_ptr<Executable>> NVPTXCompiler::RunBackend(
// temporary buffers are required to run the computation.
TF_ASSIGN_OR_RETURN(
std::unique_ptr<BufferAssignment> buffer_assignment,
- BufferAssigner::Run(module.get(), hlo_schedule->ConsumeHloOrdering(),
- BufferSizeBytesFunction(),
- /*color_alignment=*/[](LogicalBuffer::Color) {
- return kXlaAllocatedBufferAlignBytes;
- }));
+ BufferAssigner::Run(
+ module.get(), hlo_schedule->ConsumeHloOrdering(),
+ BufferSizeBytesFunction(),
+ /*color_alignment=*/
+ [](LogicalBuffer::Color) { return kXlaAllocatedBufferAlignBytes; },
+ /*allow_input_output_aliasing=*/false,
+ /*allocate_buffers_for_constants=*/true));
// BufferAssignment::Stats::ToString() and BufferAssignment::ToString()
// include headers, so no need for us to print them ourselves.
XLA_VLOG_LINES(1, buffer_assignment->GetStats().ToString());
@@ -565,6 +575,9 @@ StatusOr<std::unique_ptr<Executable>> NVPTXCompiler::RunBackend(
HloComputation* entry_computation = module->entry_computation();
IrEmitterUnnested ir_emitter(module->config(), entry_computation,
&ir_emitter_context);
+
+ TF_RETURN_IF_ERROR(ir_emitter.EmitConstantGlobals());
+
{
XLA_SCOPED_LOGGING_TIMER("NVPTXCompiler::RunBackend - IR emission");
TF_RETURN_IF_ERROR(entry_computation->Accept(&ir_emitter));
diff --git a/tensorflow/compiler/xla/service/gpu/pad_for_tensor_cores.cc b/tensorflow/compiler/xla/service/gpu/pad_for_tensor_cores.cc
new file mode 100644
index 0000000000..79f7d31816
--- /dev/null
+++ b/tensorflow/compiler/xla/service/gpu/pad_for_tensor_cores.cc
@@ -0,0 +1,233 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/compiler/xla/service/gpu/pad_for_tensor_cores.h"
+
+#include "tensorflow/compiler/xla/literal_util.h"
+#include "tensorflow/compiler/xla/service/gpu/ir_emission_utils.h"
+#include "tensorflow/compiler/xla/util.h"
+#include "tensorflow/compiler/xla/window_util.h"
+
+namespace xla {
+namespace gpu {
+
+using tensorflow::gtl::ArraySlice;
+
+// We want the input/output feature counts of an f16 conv to be factors of 8,
+// because without this cudnn can't use tensor cores on the conv.
+static constexpr int64 kDesiredNumFeaturesFactor = 8;
+
+// We won't pad a conv if doing so increases the total number of bytes in the
+// lhs, rhs, or result by more than this amount.
+//
+// TODO(jlebar): This number was tuned experimentally. It represents a
+// compromise on our current benchmarks; it speeds some up significantly, and
+// doesn't slow any down. But we can observe by changing this value that
+// there's additional room for speedups. Achieving those speedups without also
+// slowing other things down will likely require a more sophisticated heuristic,
+// possibly some form of auto-tuning.
+static constexpr double kMaxBytesTouchedIncrease = 1.2;
+
+// Pads the given dimensions in the given shape up to a multiple of
+// kDesiredNumFeaturesFactor.
+static Shape PadShape(Shape s, ArraySlice<int64> dims) {
+ for (int64 dim : dims) {
+ int64 dim_to_pad_size = s.dimensions(dim);
+ int64 new_dim_to_pad_size =
+ RoundUpToNearest(dim_to_pad_size, kDesiredNumFeaturesFactor);
+ s.set_dimensions(dim, new_dim_to_pad_size);
+ }
+ return s;
+}
+
+// Creates and returns an HLO that zero-pads one or more dimensions in the given
+// instruction so that its shape is equal to the given shape.
+//
+// Padding is added to the end of each relevant dimension.
+//
+// If the instruction already has the given shape, simply returns it without an
+// intervening pad.
+static HloInstruction* PadInstruction(HloInstruction* instr,
+ const Shape& new_shape) {
+ HloComputation* comp = instr->parent();
+
+ const Shape& shape = instr->shape();
+ auto* zero = comp->AddInstruction(HloInstruction::CreateConstant(
+ LiteralUtil::Zero(shape.element_type()).CloneToUnique()));
+
+ PaddingConfig pad_config = MakeNoPaddingConfig(ShapeUtil::Rank(shape));
+
+ bool added_padding = false;
+ for (int64 dim = 0; dim < ShapeUtil::Rank(shape); ++dim) {
+ if (shape.dimensions(dim) == new_shape.dimensions(dim)) {
+ continue;
+ }
+ CHECK_GT(new_shape.dimensions(dim), shape.dimensions(dim));
+ pad_config.mutable_dimensions(dim)->set_edge_padding_high(
+ new_shape.dimensions(dim) - shape.dimensions(dim));
+ added_padding = true;
+ }
+
+ if (!added_padding) {
+ return instr;
+ }
+ return comp->AddInstruction(
+ HloInstruction::CreatePad(new_shape, instr, zero, pad_config));
+}
+
+// Pads the input/output feature dimensions of the given cudnn convolution
+// custom-call to be multiples of kDesiredNumFeaturesFactor.
+static StatusOr<bool> PadFeaturesDims(HloInstruction* conv) {
+ CHECK_EQ(0, conv->shape().tuple_shapes(1).dimensions(0))
+ << "conv must use 0 scratch bytes, i.e. this pass must be run "
+ "before CudnnConvolutionAlgorithmPicker.";
+
+ const auto& target = conv->custom_call_target();
+ const auto& dnums = conv->convolution_dimension_numbers();
+ auto* lhs = conv->mutable_operand(0);
+ auto* rhs = conv->mutable_operand(1);
+ const Shape& result_shape = conv->shape().tuple_shapes(0);
+
+ Shape new_lhs_shape = [&] {
+ if (target == kCudnnConvForwardCallTarget ||
+ target == kCudnnConvBackwardFilterCallTarget) {
+ // LHS is "input".
+ return PadShape(lhs->shape(), {dnums.input_feature_dimension()});
+ }
+ CHECK_EQ(target, kCudnnConvBackwardInputCallTarget);
+ // LHS is "output".
+ return PadShape(lhs->shape(), {dnums.output_feature_dimension()});
+ }();
+
+ Shape new_rhs_shape = [&] {
+ if (target == kCudnnConvForwardCallTarget ||
+ target == kCudnnConvBackwardInputCallTarget) {
+ // RHS is "filter".
+ return PadShape(rhs->shape(), {dnums.kernel_input_feature_dimension(),
+ dnums.kernel_output_feature_dimension()});
+ }
+ CHECK_EQ(target, kCudnnConvBackwardFilterCallTarget);
+ // RHS is "output".
+ return PadShape(rhs->shape(), {dnums.output_feature_dimension()});
+ }();
+
+ if (ShapeUtil::Equal(lhs->shape(), new_lhs_shape) &&
+ ShapeUtil::Equal(rhs->shape(), new_rhs_shape)) {
+ VLOG(3) << "No need to pad features of " << conv->ToString();
+ return false;
+ }
+
+ Shape new_result_shape = [&] {
+ if (target == kCudnnConvForwardCallTarget) {
+ // Result is "output".
+ return PadShape(result_shape, {dnums.output_feature_dimension()});
+ }
+ if (target == kCudnnConvBackwardInputCallTarget) {
+ // Result is "input".
+ return PadShape(result_shape, {dnums.input_feature_dimension()});
+ }
+ CHECK_EQ(target, kCudnnConvBackwardFilterCallTarget);
+ // Result is "filter".
+ return PadShape(result_shape, {dnums.kernel_input_feature_dimension(),
+ dnums.kernel_output_feature_dimension()});
+ }();
+
+ // Check that padding wouldn't increase the total bytes read/written by this
+ // operation too much.
+ auto check_size_increase = [&](const Shape& old_shape,
+ const Shape& new_shape) {
+ int64 old_bytes = ShapeUtil::ByteSizeOf(old_shape);
+ int64 new_bytes = ShapeUtil::ByteSizeOf(new_shape);
+ if (new_bytes <= old_bytes * kMaxBytesTouchedIncrease) {
+ return true;
+ }
+ VLOG(3) << "Not padding convolution; doing so would change input / result "
+ "shape from "
+ << ShapeUtil::HumanString(old_shape) << " to "
+ << ShapeUtil::HumanString(new_shape) << ", a size increase of "
+ << new_bytes / static_cast<double>(old_bytes) << "x > "
+ << kMaxBytesTouchedIncrease << "x: " << conv->ToString();
+ return false;
+ };
+ if (!check_size_increase(lhs->shape(), new_lhs_shape) ||
+ !check_size_increase(rhs->shape(), new_rhs_shape) ||
+ !check_size_increase(result_shape, new_result_shape)) {
+ return false;
+ }
+
+ // OK, let's do the transformation!
+
+ auto* new_lhs = PadInstruction(lhs, new_lhs_shape);
+ auto* new_rhs = PadInstruction(rhs, new_rhs_shape);
+ CHECK(new_lhs != lhs || new_rhs != rhs)
+ << "We should have had to pad either LHS or RHS.";
+
+ auto add = [&](std::unique_ptr<HloInstruction> new_instr) {
+ return conv->parent()->AddInstruction(std::move(new_instr));
+ };
+
+ Shape new_conv_shape = ShapeUtil::MakeTupleShape(
+ {new_result_shape, ShapeUtil::MakeShape(U8, {0})});
+ auto* new_conv =
+ add(conv->CloneWithNewOperands(new_conv_shape, {new_lhs, new_rhs}));
+
+ // Slice the new conv result if necessary, keeping in mind that new_conv has
+ // tuple shape (new_result_shape, u8[0]).
+ if (!ShapeUtil::Equal(result_shape, new_result_shape)) {
+ std::vector<int64> start_indices(result_shape.dimensions_size(), 0);
+ std::vector<int64> end_indices(result_shape.dimensions().begin(),
+ result_shape.dimensions().end());
+ std::vector<int64> strides(result_shape.dimensions_size(), 1);
+
+ auto* new_conv_result = add(
+ HloInstruction::CreateGetTupleElement(new_result_shape, new_conv, 0));
+ auto* empty_temp_buffer =
+ add(HloInstruction::CreateConstant(LiteralUtil::CreateR1<uint8>({})));
+ auto* sliced_result = add(HloInstruction::CreateSlice(
+ result_shape, new_conv_result, start_indices, end_indices, strides));
+ new_conv =
+ add(HloInstruction::CreateTuple({sliced_result, empty_temp_buffer}));
+ }
+
+ VLOG(2) << "Padded features of " << conv->ToString() << ", replaced with "
+ << new_conv->ToString();
+ TF_RETURN_IF_ERROR(conv->parent()->ReplaceInstruction(conv, new_conv));
+ return true;
+}
+
+static std::vector<HloInstruction*> GetRelevantConvs(HloComputation* comp) {
+ std::vector<HloInstruction*> convs;
+ for (HloInstruction* instr : comp->instructions()) {
+ if (IsCustomCallToDnnConvolution(*instr) &&
+ instr->operand(0)->shape().element_type() == F16) {
+ convs.push_back(instr);
+ }
+ }
+ return convs;
+}
+
+StatusOr<bool> PadForTensorCores::Run(HloModule* module) {
+ bool changed = false;
+ for (HloComputation* comp : module->MakeNonfusionComputations()) {
+ for (HloInstruction* conv : GetRelevantConvs(comp)) {
+ TF_ASSIGN_OR_RETURN(bool result, PadFeaturesDims(conv));
+ changed |= result;
+ }
+ }
+ return changed;
+}
+
+} // namespace gpu
+} // namespace xla
diff --git a/tensorflow/compiler/xla/service/gpu/pad_for_tensor_cores.h b/tensorflow/compiler/xla/service/gpu/pad_for_tensor_cores.h
new file mode 100644
index 0000000000..192359f026
--- /dev/null
+++ b/tensorflow/compiler/xla/service/gpu/pad_for_tensor_cores.h
@@ -0,0 +1,45 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#ifndef TENSORFLOW_COMPILER_XLA_SERVICE_GPU_PAD_FOR_TENSOR_CORES_H_
+#define TENSORFLOW_COMPILER_XLA_SERVICE_GPU_PAD_FOR_TENSOR_CORES_H_
+
+#include "tensorflow/compiler/xla/service/hlo_pass_interface.h"
+
+namespace xla {
+namespace gpu {
+
+// Ensures that f16 cudnn convolutions have input/output channel dimensions that
+// are multiples of 8, inserting pads/slices as necessary.
+//
+// This is useful primarily for Volta and newer GPUs, where tensor cores can
+// only be used if the channel dims are multiples of 8. It's probably the
+// opposite of useful on other GPUs, so you should check what GPU you're
+// targeting before running this pass.
+//
+// TODO(jlebar): Also pad dots.
+class PadForTensorCores : public HloPassInterface {
+ public:
+ tensorflow::StringPiece name() const override {
+ return "pad for tensor cores";
+ }
+
+ StatusOr<bool> Run(HloModule* module) override;
+};
+
+} // namespace gpu
+} // namespace xla
+
+#endif // TENSORFLOW_COMPILER_XLA_SERVICE_GPU_PAD_FOR_TENSOR_CORES_H_
diff --git a/tensorflow/compiler/xla/service/gpu/pad_for_tensor_cores_test.cc b/tensorflow/compiler/xla/service/gpu/pad_for_tensor_cores_test.cc
new file mode 100644
index 0000000000..99e7580b82
--- /dev/null
+++ b/tensorflow/compiler/xla/service/gpu/pad_for_tensor_cores_test.cc
@@ -0,0 +1,164 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/compiler/xla/service/gpu/pad_for_tensor_cores.h"
+
+#include "tensorflow/compiler/xla/service/gpu/ir_emission_utils.h"
+#include "tensorflow/compiler/xla/service/hlo_matchers.h"
+#include "tensorflow/compiler/xla/service/hlo_parser.h"
+#include "tensorflow/compiler/xla/status_macros.h"
+#include "tensorflow/compiler/xla/tests/hlo_verified_test_base.h"
+#include "tensorflow/compiler/xla/util.h"
+
+namespace xla {
+namespace gpu {
+namespace {
+
+namespace op = xla::testing::opcode_matchers;
+using ::testing::_;
+
+using PadForTensorCoresTest = HloVerifiedTestBase;
+
+TEST_F(PadForTensorCoresTest, PadF16ForwardConvInputChannels) {
+ ParseAndVerifyModule(R"(
+ HloModule TestModule
+
+ ENTRY TestComputation {
+ input = f16[10,20,30,41] parameter(0)
+ filter = f16[2,2,41,40] parameter(1)
+ ROOT result = (f16[10,20,30,40], u8[0]) custom-call(input, filter),
+ window={size=2x2}, dim_labels=b01f_01io->b01f,
+ custom_call_target="__cudnn$convForward"
+ })");
+ EXPECT_TRUE(PadForTensorCores().Run(&module()).ValueOrDie());
+ auto* root = module().entry_computation()->root_instruction();
+
+ SCOPED_TRACE(module().ToString());
+ EXPECT_THAT(root, op::CustomCall(kCudnnConvForwardCallTarget,
+ op::Pad(op::Parameter(0), _),
+ op::Pad(op::Parameter(1), _)));
+ EXPECT_TRUE(ShapeUtil::Equal(root->operand(0)->shape(),
+ ShapeUtil::MakeShape(F16, {10, 20, 30, 48})));
+ EXPECT_TRUE(ShapeUtil::Equal(root->operand(1)->shape(),
+ ShapeUtil::MakeShape(F16, {2, 2, 48, 40})));
+}
+
+TEST_F(PadForTensorCoresTest, PadF16BackwardInputConvOutputChannels) {
+ ParseAndVerifyModule(R"(
+ HloModule TestModule
+
+ ENTRY TestComputation {
+ output = f16[10,20,30,41] parameter(0)
+ filter = f16[2,2,40,41] parameter(1)
+ ROOT result = (f16[10,20,30,40], u8[0]) custom-call(output, filter),
+ window={size=2x2}, dim_labels=b01f_01io->b01f,
+ custom_call_target="__cudnn$convBackwardInput"
+ })");
+ EXPECT_TRUE(PadForTensorCores().Run(&module()).ValueOrDie());
+ auto* root = module().entry_computation()->root_instruction();
+ EXPECT_THAT(root, op::CustomCall(kCudnnConvBackwardInputCallTarget,
+ op::Pad(op::Parameter(0), _),
+ op::Pad(op::Parameter(1), _)));
+ EXPECT_TRUE(ShapeUtil::Equal(root->operand(0)->shape(),
+ ShapeUtil::MakeShape(F16, {10, 20, 30, 48})));
+ EXPECT_TRUE(ShapeUtil::Equal(root->operand(1)->shape(),
+ ShapeUtil::MakeShape(F16, {2, 2, 40, 48})));
+}
+
+TEST_F(PadForTensorCoresTest, PadF16ForwardConvOutputChannels) {
+ ParseAndVerifyModule(R"(
+ HloModule TestModule
+
+ ENTRY TestComputation {
+ input = f16[10,20,30,40] parameter(0)
+ filter = f16[2,2,40,41] parameter(1)
+ ROOT result = (f16[10,20,30,41], u8[0]) custom-call(input, filter),
+ window={size=2x2}, dim_labels=b01f_01io->b01f,
+ custom_call_target="__cudnn$convForward"
+ })");
+ EXPECT_TRUE(PadForTensorCores().Run(&module()).ValueOrDie());
+ auto* root = module().entry_computation()->root_instruction();
+ EXPECT_THAT(root, op::Tuple(op::Slice(op::GetTupleElement(op::CustomCall(
+ kCudnnConvForwardCallTarget, op::Parameter(0),
+ op::Pad(op::Parameter(1), _)))),
+ _));
+}
+
+TEST_F(PadForTensorCoresTest, PadF16BackwardInputConvInputChannels) {
+ ParseAndVerifyModule(R"(
+ HloModule TestModule
+
+ ENTRY TestComputation {
+ output = f16[10,20,30,40] parameter(0)
+ filter = f16[2,2,41,40] parameter(1)
+ result = (f16[10,20,30,41], u8[0]) custom-call(output, filter),
+ window={size=2x2}, dim_labels=b01f_01io->b01f,
+ custom_call_target="__cudnn$convBackwardInput"
+ ROOT gte = f16[10,20,30,41] get-tuple-element(result), index=0
+ })");
+ EXPECT_TRUE(PadForTensorCores().Run(&module()).ValueOrDie());
+ auto* root = module().entry_computation()->root_instruction();
+ EXPECT_THAT(root, op::GetTupleElement(op::Tuple(
+ op::Slice(op::GetTupleElement(op::CustomCall(
+ kCudnnConvBackwardInputCallTarget, op::Parameter(0),
+ op::Pad(op::Parameter(1), _)))),
+ _)));
+}
+
+TEST_F(PadForTensorCoresTest, PadF16BackwardFilterConvInputChannels) {
+ ParseAndVerifyModule(R"(
+ HloModule TestModule
+
+ ENTRY TestComputation {
+ input = f16[10,20,30,41] parameter(0)
+ output = f16[10,20,30,40] parameter(1)
+ result = (f16[2,2,41,40], u8[0]) custom-call(input, output),
+ window={size=2x2}, dim_labels=b01f_01io->b01f,
+ custom_call_target="__cudnn$convBackwardFilter"
+ ROOT gte = f16[2,2,41,40] get-tuple-element(result), index=0
+ })");
+ EXPECT_TRUE(PadForTensorCores().Run(&module()).ValueOrDie());
+ auto* root = module().entry_computation()->root_instruction();
+ EXPECT_THAT(root, op::GetTupleElement(op::Tuple(
+ op::Slice(op::GetTupleElement(op::CustomCall(
+ kCudnnConvBackwardFilterCallTarget,
+ op::Pad(op::Parameter(0), _), op::Parameter(1)))),
+ _)));
+}
+
+TEST_F(PadForTensorCoresTest, PadF16BackwardFilterConvOutputChannels) {
+ ParseAndVerifyModule(R"(
+ HloModule TestModule
+
+ ENTRY TestComputation {
+ input = f16[10,20,30,40] parameter(0)
+ output = f16[10,20,30,41] parameter(1)
+ result = (f16[2,2,40,41], u8[0]) custom-call(input, output),
+ window={size=2x2}, dim_labels=b01f_01io->b01f,
+ custom_call_target="__cudnn$convBackwardFilter"
+ ROOT gte = f16[2,2,40,41] get-tuple-element(result), index=0
+ })");
+ EXPECT_TRUE(PadForTensorCores().Run(&module()).ValueOrDie());
+ auto* root = module().entry_computation()->root_instruction();
+ EXPECT_THAT(root, op::GetTupleElement(op::Tuple(
+ op::Slice(op::GetTupleElement(op::CustomCall(
+ kCudnnConvBackwardFilterCallTarget,
+ op::Parameter(0), op::Pad(op::Parameter(1), _)))),
+ _)));
+}
+
+} // anonymous namespace
+} // namespace gpu
+} // namespace xla
diff --git a/tensorflow/compiler/xla/service/gpu/stream_assignment.cc b/tensorflow/compiler/xla/service/gpu/stream_assignment.cc
index e4cfc6999f..0806dd5161 100644
--- a/tensorflow/compiler/xla/service/gpu/stream_assignment.cc
+++ b/tensorflow/compiler/xla/service/gpu/stream_assignment.cc
@@ -33,13 +33,13 @@ int StreamAssignment::StreamNumberForHlo(const HloInstruction& hlo) const {
}
void StreamAssignment::AssignStreamToHlo(const HloInstruction* hlo,
- int stream_no) {
- CHECK_GE(stream_no, 0);
- if (stream_no >= stream_count_) {
- stream_count_ = stream_no + 1;
+ int stream_num) {
+ CHECK_GE(stream_num, 0);
+ if (stream_num >= stream_count_) {
+ stream_count_ = stream_num + 1;
}
- InsertOrDie(&hlo_to_stream_number_, hlo, stream_no);
- VLOG(2) << "Assign stream #" << stream_no << " to " << hlo->ToString();
+ InsertOrDie(&hlo_to_stream_number_, hlo, stream_num);
+ VLOG(2) << "Assign stream #" << stream_num << " to " << hlo->ToString();
}
namespace {
@@ -51,6 +51,12 @@ bool CanRunConcurrently(const HloInstruction& a, const HloInstruction& b,
return !reachability.IsConnected(&a, &b);
}
+constexpr int kInvalidStreamNum = -1;
+// Returns true iff `stream_num` is an invalid stream number.
+inline bool IsStreamNumValid(int stream_num) {
+ return stream_num != kInvalidStreamNum;
+}
+
// Returns which existing stream to assign to `hlo`, or -1 if a stream is not
// needed. `stream_assignment` is the existing stream assignment for all
// instructions topologically before `hlo`. `seen_gemms` contains all GEMMs that
@@ -62,7 +68,7 @@ int ComputeStreamToAssign(
if (hlo.opcode() == HloOpcode::kParameter ||
hlo.opcode() == HloOpcode::kConstant) {
// kParameter and kConstant do not need a thunk.
- return -1;
+ return kInvalidStreamNum;
}
if (hlo.GetModule()
@@ -75,17 +81,17 @@ int ComputeStreamToAssign(
if (!ImplementedAsGemm(hlo)) {
// If `hlo` is not implemented as a GEMM, keep it close to its operands to
// avoid excessive synchronization.
- int stream_no = -1;
+ int stream_num = -1;
for (const auto* operand : hlo.operands()) {
if (stream_assignment.HasStreamAssigned(*operand)) {
- stream_no =
- std::max(stream_no, stream_assignment.StreamNumberForHlo(*operand));
+ stream_num = std::max(stream_num,
+ stream_assignment.StreamNumberForHlo(*operand));
}
}
- if (stream_no == -1) {
- stream_no = 0;
+ if (!IsStreamNumValid(stream_num)) {
+ stream_num = 0;
}
- return stream_no;
+ return stream_num;
}
// Assign different streams to concurrent GEMMs. The code below uses a
@@ -94,17 +100,17 @@ int ComputeStreamToAssign(
// `hlo` a different stream.
std::set<int> forbidden_stream_numbers;
for (const auto* seen_gemm : seen_gemms) {
- int stream_no = stream_assignment.StreamNumberForHlo(*seen_gemm);
- if (!forbidden_stream_numbers.count(stream_no) &&
+ int stream_num = stream_assignment.StreamNumberForHlo(*seen_gemm);
+ if (!forbidden_stream_numbers.count(stream_num) &&
CanRunConcurrently(*seen_gemm, hlo, reachability)) {
- forbidden_stream_numbers.insert(stream_no);
+ forbidden_stream_numbers.insert(stream_num);
}
}
- for (int stream_no = 0; stream_no < stream_assignment.StreamCount();
- ++stream_no) {
- if (!forbidden_stream_numbers.count(stream_no)) {
- return stream_no;
+ for (int stream_num = 0; stream_num < stream_assignment.StreamCount();
+ ++stream_num) {
+ if (!forbidden_stream_numbers.count(stream_num)) {
+ return stream_num;
}
}
return stream_assignment.StreamCount();
@@ -118,11 +124,27 @@ std::unique_ptr<StreamAssignment> AssignStreams(const HloModule& module) {
std::unique_ptr<HloReachabilityMap> reachability =
computation.ComputeReachability();
std::vector<const HloInstruction*> seen_gemms;
+ // The execution of different RNG Hlo instructions in the same module updates
+ // a common global variable. To avoid a race condition, we simply assign all
+ // RNG kernels to the same stream to make them run sequentially.
+ //
+ // TODO(b/111791052): If we remove such a common variable, we will need to
+ // clean up the code here.
+ int stream_num_for_rng = kInvalidStreamNum;
for (const auto* hlo : computation.MakeInstructionPostOrder()) {
- int stream_no = ComputeStreamToAssign(*hlo, *stream_assignment,
- *reachability, seen_gemms);
- if (stream_no != -1) {
- stream_assignment->AssignStreamToHlo(hlo, stream_no);
+ // If we ever enable fusion of RNG instructions, we will need to extend this
+ // code to look inside a fused instruction.
+ int stream_num = (hlo->opcode() == HloOpcode::kRng &&
+ IsStreamNumValid(stream_num_for_rng))
+ ? stream_num_for_rng
+ : ComputeStreamToAssign(*hlo, *stream_assignment,
+ *reachability, seen_gemms);
+ if (IsStreamNumValid(stream_num)) {
+ stream_assignment->AssignStreamToHlo(hlo, stream_num);
+ if (hlo->opcode() == HloOpcode::kRng &&
+ !IsStreamNumValid(stream_num_for_rng)) {
+ stream_num_for_rng = stream_num;
+ }
}
if (ImplementedAsGemm(*hlo)) {
seen_gemms.push_back(hlo);
diff --git a/tensorflow/compiler/xla/service/gpu/stream_executor_util.cc b/tensorflow/compiler/xla/service/gpu/stream_executor_util.cc
index a50ddf6ac6..05b305ea4c 100644
--- a/tensorflow/compiler/xla/service/gpu/stream_executor_util.cc
+++ b/tensorflow/compiler/xla/service/gpu/stream_executor_util.cc
@@ -20,10 +20,17 @@ limitations under the License.
namespace xla {
namespace gpu {
-using stream_executor::dnn::DataLayout;
-using stream_executor::dnn::DataLayoutString;
-using stream_executor::dnn::FilterLayout;
-using stream_executor::dnn::FilterLayoutString;
+using se::dnn::DataLayout;
+using se::dnn::DataLayoutString;
+using se::dnn::FilterLayout;
+using se::dnn::FilterLayoutString;
+
+bool IsVoltaOrLater(const se::StreamExecutor& stream_executor) {
+ int major, minor;
+ CHECK(stream_executor.GetDeviceDescription().cuda_compute_capability(&major,
+ &minor));
+ return major >= 7;
+}
StatusOr<std::tuple<Layout, Layout, Layout>>
StreamExecutorConvLayoutsToXlaLayouts(const ConvolutionDimensionNumbers& dnums,
diff --git a/tensorflow/compiler/xla/service/gpu/stream_executor_util.h b/tensorflow/compiler/xla/service/gpu/stream_executor_util.h
index 39a6a38d00..1fc46bafa1 100644
--- a/tensorflow/compiler/xla/service/gpu/stream_executor_util.h
+++ b/tensorflow/compiler/xla/service/gpu/stream_executor_util.h
@@ -17,6 +17,7 @@ limitations under the License.
#define TENSORFLOW_COMPILER_XLA_SERVICE_GPU_STREAM_EXECUTOR_UTIL_H_
#include "tensorflow/compiler/xla/statusor.h"
+#include "tensorflow/compiler/xla/types.h"
#include "tensorflow/compiler/xla/xla_data.pb.h"
#include "tensorflow/core/platform/stream_executor_no_cuda.h"
@@ -25,18 +26,20 @@ limitations under the License.
namespace xla {
namespace gpu {
+// Returns true if the given StreamExecutor is for a Volta or newer nvidia GPU.
+bool IsVoltaOrLater(const se::StreamExecutor& stream_exec);
+
// Returns (input, filter, output) XLA Layout protos given the StreamExecutor
// layouts.
StatusOr<std::tuple<Layout, Layout, Layout>>
StreamExecutorConvLayoutsToXlaLayouts(const ConvolutionDimensionNumbers& dnums,
- stream_executor::dnn::DataLayout input,
- stream_executor::dnn::FilterLayout filter,
- stream_executor::dnn::DataLayout output);
+ se::dnn::DataLayout input,
+ se::dnn::FilterLayout filter,
+ se::dnn::DataLayout output);
// Returns (input, filter, output) StreamExecutor layouts given the XLA layouts.
-StatusOr<std::tuple<stream_executor::dnn::DataLayout,
- stream_executor::dnn::FilterLayout,
- stream_executor::dnn::DataLayout>>
+StatusOr<
+ std::tuple<se::dnn::DataLayout, se::dnn::FilterLayout, se::dnn::DataLayout>>
XlaConvLayoutsToStreamExecutorLayouts(const ConvolutionDimensionNumbers& dnums,
const Layout& input, const Layout& filter,
const Layout& output);
diff --git a/tensorflow/compiler/xla/service/gpu/tests/BUILD b/tensorflow/compiler/xla/service/gpu/tests/BUILD
index 686c3c16c9..4fad3f46cf 100644
--- a/tensorflow/compiler/xla/service/gpu/tests/BUILD
+++ b/tensorflow/compiler/xla/service/gpu/tests/BUILD
@@ -111,8 +111,8 @@ tf_cc_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/client/lib:arithmetic",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/core:lib",
diff --git a/tensorflow/compiler/xla/service/gpu/tests/infeed_test.cc b/tensorflow/compiler/xla/service/gpu/tests/infeed_test.cc
index ba5cd2d84d..9072b30317 100644
--- a/tensorflow/compiler/xla/service/gpu/tests/infeed_test.cc
+++ b/tensorflow/compiler/xla/service/gpu/tests/infeed_test.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/test_helpers.h"
diff --git a/tensorflow/compiler/xla/service/gpu/while_thunk.cc b/tensorflow/compiler/xla/service/gpu/while_thunk.cc
index 1315a4183a..d81d87e7dc 100644
--- a/tensorflow/compiler/xla/service/gpu/while_thunk.cc
+++ b/tensorflow/compiler/xla/service/gpu/while_thunk.cc
@@ -57,6 +57,7 @@ Status WhileThunk::ExecuteOnStream(const BufferAllocations& buffer_allocations,
while (true) {
// Invoke thunk sequence for while 'condition' computation.
profiler->StartHloComputation();
+ VLOG(3) << "Executing condition computation";
TF_RETURN_IF_ERROR(condition_thunk_sequence_->ExecuteOnStream(
buffer_allocations, stream, profiler));
profiler->FinishHloComputation(hlo_instruction()->while_condition());
@@ -64,6 +65,7 @@ Status WhileThunk::ExecuteOnStream(const BufferAllocations& buffer_allocations,
// Copy the result of condition computation and break the loop if 'false'.
bool condition_result;
stream->ThenMemcpy(&condition_result, condition_result_data, sizeof(bool));
+ VLOG(3) << "condition_result = " << condition_result;
Status block_status = stream->BlockHostUntilDone();
if (!block_status.ok()) {
return InternalError(
@@ -78,6 +80,7 @@ Status WhileThunk::ExecuteOnStream(const BufferAllocations& buffer_allocations,
// We measure the time of one execution of the while body computation. The
// while body may be executed more than once, the last measurement "wins".
profiler->StartHloComputation();
+ VLOG(3) << "Executing body computation";
// Invoke thunk sequence for while 'body' computation, and pass on
// 'profiler' to measure the timing of the thunks in 'body_thunk_sequence_'.
TF_RETURN_IF_ERROR(body_thunk_sequence_->ExecuteOnStream(buffer_allocations,
diff --git a/tensorflow/compiler/xla/service/hlo_cost_analysis_test.cc b/tensorflow/compiler/xla/service/hlo_cost_analysis_test.cc
index 9fd0363f57..2c854eea18 100644
--- a/tensorflow/compiler/xla/service/hlo_cost_analysis_test.cc
+++ b/tensorflow/compiler/xla/service/hlo_cost_analysis_test.cc
@@ -22,8 +22,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/client_library.h"
#include "tensorflow/compiler/xla/client/local_client.h"
#include "tensorflow/compiler/xla/client/padding.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/service/hlo_module.h"
#include "tensorflow/compiler/xla/service/local_service.h"
#include "tensorflow/compiler/xla/service/service.h"
diff --git a/tensorflow/compiler/xla/service/hlo_dataflow_analysis.cc b/tensorflow/compiler/xla/service/hlo_dataflow_analysis.cc
index de1a32d8bd..bbfb0c253f 100644
--- a/tensorflow/compiler/xla/service/hlo_dataflow_analysis.cc
+++ b/tensorflow/compiler/xla/service/hlo_dataflow_analysis.cc
@@ -1017,19 +1017,17 @@ bool HloDataflowAnalysis::CanShareOperandBufferWithUser(
}
if (user->opcode() == HloOpcode::kFusion) {
+ if (fusion_can_share_buffer_ != nullptr) {
+ return fusion_can_share_buffer_(user, operand);
+ }
// Get the parameter associated with 'operand';
HloInstruction* fusion_param =
user->fused_parameter(user->operand_index(operand));
const HloValue& value = GetValueDefinedAt(fusion_param, operand_index);
- if (value.uses().size() != 1) {
- if (MultiDynamicSliceUseShareSameIndices(value.uses())) {
- return true;
- }
- return false;
+ if (MultiDynamicSliceUseShareSameIndices(value.uses())) {
+ return true;
}
- const HloUse& use = value.uses()[0];
-
if (user->fusion_kind() == HloInstruction::FusionKind::kLoop ||
user->fusion_kind() == HloInstruction::FusionKind::kInput) {
if (user->fused_expression_root()->opcode() ==
@@ -1039,13 +1037,17 @@ bool HloDataflowAnalysis::CanShareOperandBufferWithUser(
// Returns true iff there is exactly one use of 'operand' at shape index
// 'operand_index', and this singleton use is the fused root at operand
// index 0.
- return use.instruction == user->fused_expression_root() &&
- use.operand_number == 0;
- } else {
- return AreTransitiveUsesElementwiseOrTuple(fusion_param);
+ if (value.uses().size() == 1) {
+ const HloUse& use = value.uses()[0];
+ return use.instruction == user->fused_expression_root() &&
+ use.operand_number == 0;
+ }
+ return false;
}
- } else if (user->fusion_kind() == HloInstruction::FusionKind::kOutput &&
- user->fused_expression_root()->opcode() == HloOpcode::kAdd) {
+ return AreTransitiveUsesElementwiseOrTuple(fusion_param);
+ }
+ if (user->fusion_kind() == HloInstruction::FusionKind::kOutput &&
+ user->fused_expression_root()->opcode() == HloOpcode::kAdd) {
// Output fusion with kAdd fused root.
// Check if one operand of kAdd fused root is kDot or kConvolution.
@@ -1066,11 +1068,12 @@ bool HloDataflowAnalysis::CanShareOperandBufferWithUser(
// Returns true iff there is exactly one use of 'operand' at shape index
// 'operand_index', and this singleton use is the fused root (at operand
// index 'other_add_operand_index').
- return use.instruction == user->fused_expression_root() &&
- use.operand_number == other_add_operand_index;
- } else if (fusion_can_share_buffer_ != nullptr &&
- fusion_can_share_buffer_(user, operand)) {
- return true;
+ if (value.uses().size() == 1) {
+ const HloUse& use = value.uses()[0];
+ return use.instruction == user->fused_expression_root() &&
+ use.operand_number == other_add_operand_index;
+ }
+ return false;
}
}
@@ -1081,6 +1084,21 @@ bool HloDataflowAnalysis::CanShareOperandBufferWithUser(
std::vector<int64> operand_indices = user->OperandIndices(operand);
return operand_indices.size() == 1 && operand_indices[0] == 0;
}
+ if (user->opcode() == HloOpcode::kSort) {
+ // Only valid if there are no other users.
+ if (operand->users().size() != 1) {
+ return false;
+ }
+ // If we only sort keys, the output of sort is not a tuple, so we can always
+ // share the buffer.
+ if (user->operand_count() == 1) {
+ return true;
+ }
+ CHECK(!user_index.empty());
+ // Only share with the right tuple element buffer.
+ std::vector<int64> operand_indices = user->OperandIndices(operand);
+ return operand_indices.size() == 1 && user_index[0] == operand_indices[0];
+ }
if (user->opcode() == HloOpcode::kCall) {
// Get all uses of value defined by 'operand' at 'operand_index'.
const auto& uses = GetValueDefinedAt(operand, operand_index).uses();
diff --git a/tensorflow/compiler/xla/service/hlo_dataflow_analysis_test.cc b/tensorflow/compiler/xla/service/hlo_dataflow_analysis_test.cc
index 37bc2d2c9d..2ec31a9148 100644
--- a/tensorflow/compiler/xla/service/hlo_dataflow_analysis_test.cc
+++ b/tensorflow/compiler/xla/service/hlo_dataflow_analysis_test.cc
@@ -2232,6 +2232,48 @@ TEST_F(CanShareOperandBufferWithUserTest, DynamicUpdateSliceCanShare) {
dataflow_analysis_->CanShareOperandBufferWithUser(starts, {}, dus, {}));
}
+TEST_F(CanShareOperandBufferWithUserTest, SortCanShare) {
+ auto builder = HloComputation::Builder(TestName());
+
+ Shape keys_shape = ShapeUtil::MakeShape(F32, {8});
+ auto keys = builder.AddInstruction(
+ HloInstruction::CreateParameter(0, keys_shape, "keys"));
+ auto sort =
+ builder.AddInstruction(HloInstruction::CreateSort(keys_shape, 0, keys));
+
+ BuildModuleAndRunAnalysis(builder.Build());
+
+ EXPECT_TRUE(
+ dataflow_analysis_->CanShareOperandBufferWithUser(keys, {}, sort, {}));
+}
+
+TEST_F(CanShareOperandBufferWithUserTest, SortCanShareWithTupleUser) {
+ auto builder = HloComputation::Builder(TestName());
+
+ Shape keys_shape = ShapeUtil::MakeShape(F32, {8});
+ Shape values_shape = ShapeUtil::MakeShape(F32, {8});
+ auto keys = builder.AddInstruction(
+ HloInstruction::CreateParameter(0, keys_shape, "keys"));
+ auto values = builder.AddInstruction(
+ HloInstruction::CreateParameter(1, values_shape, "values"));
+ auto sort = builder.AddInstruction(HloInstruction::CreateSort(
+ ShapeUtil::MakeTupleShape({keys_shape, values_shape}), 0, keys, values));
+
+ BuildModuleAndRunAnalysis(builder.Build());
+
+ // The buffer for the keys can be shared with the first tuple entry.
+ EXPECT_TRUE(
+ dataflow_analysis_->CanShareOperandBufferWithUser(keys, {}, sort, {0}));
+ // The buffer for the values can be shared with the second tuple entry.
+ EXPECT_TRUE(
+ dataflow_analysis_->CanShareOperandBufferWithUser(values, {}, sort, {1}));
+ // Verify that the buffers are not shared with the "wrong" tuple entry.
+ EXPECT_FALSE(
+ dataflow_analysis_->CanShareOperandBufferWithUser(keys, {}, sort, {1}));
+ EXPECT_FALSE(
+ dataflow_analysis_->CanShareOperandBufferWithUser(values, {}, sort, {0}));
+}
+
TEST_F(CanShareOperandBufferWithUserTest, FusedDotAdd) {
auto builder = HloComputation::Builder(TestName());
Shape data_shape = ShapeUtil::MakeShape(F32, {2, 2});
diff --git a/tensorflow/compiler/xla/service/hlo_evaluator_test.cc b/tensorflow/compiler/xla/service/hlo_evaluator_test.cc
index 5f575b24a1..cba72469ce 100644
--- a/tensorflow/compiler/xla/service/hlo_evaluator_test.cc
+++ b/tensorflow/compiler/xla/service/hlo_evaluator_test.cc
@@ -21,7 +21,7 @@ limitations under the License.
#include <utility>
#include <vector>
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/reference_util.h"
#include "tensorflow/compiler/xla/service/hlo_computation.h"
diff --git a/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h b/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h
index f5e477e115..d5b4be7e12 100644
--- a/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h
+++ b/tensorflow/compiler/xla/service/hlo_evaluator_typed_visitor.h
@@ -2078,10 +2078,6 @@ class HloEvaluatorTypedVisitor : public DfsHloVisitorWithDefault {
start_indices_typed.end());
// Clamp the start indices so the slice is in-bounds w.r.t the operand.
-
- // TODO(b/74360564): This is implementation defined behavior, but is
- // currently respected by all implementations. Change this if we ever decide
- // to officially document different behavior.
for (int64 i = 0; i < start.size(); ++i) {
start[i] = std::min<int64>(
std::max(int64{0}, start[i]),
@@ -2115,10 +2111,6 @@ class HloEvaluatorTypedVisitor : public DfsHloVisitorWithDefault {
start_indices_typed.end());
// Clamp the update start indices so the slice is in-bounds w.r.t the
// operand.
-
- // TODO(b/74360564): This is implementation defined behavior, but is
- // currently respected by all implementations. Change this if we ever decide
- // to oficially document different behavior.
for (int64 i = 0; i < rank; ++i) {
start[i] = std::min<int64>(
std::max<int64>(0, start[i]),
diff --git a/tensorflow/compiler/xla/service/hlo_instruction.h b/tensorflow/compiler/xla/service/hlo_instruction.h
index 30bff286c2..70441b879d 100644
--- a/tensorflow/compiler/xla/service/hlo_instruction.h
+++ b/tensorflow/compiler/xla/service/hlo_instruction.h
@@ -447,8 +447,7 @@ class HloInstruction {
HloComputation* reduce_computation,
tensorflow::gtl::ArraySlice<int64> replica_group_ids,
tensorflow::StringPiece barrier,
- const tensorflow::gtl::optional<int64>& all_reduce_id =
- tensorflow::gtl::nullopt);
+ const tensorflow::gtl::optional<int64>& all_reduce_id);
// Creates a conversion instruction, where operand is the data to convert and
// shape is the target shape for the conversion.
diff --git a/tensorflow/compiler/xla/service/hlo_instructions.h b/tensorflow/compiler/xla/service/hlo_instructions.h
index e4031f04d5..132e767420 100644
--- a/tensorflow/compiler/xla/service/hlo_instructions.h
+++ b/tensorflow/compiler/xla/service/hlo_instructions.h
@@ -224,8 +224,7 @@ class HloAllReduceInstruction : public HloInstruction {
HloComputation* reduce_computation,
tensorflow::gtl::ArraySlice<int64> replica_group_ids,
tensorflow::StringPiece barrier,
- const tensorflow::gtl::optional<int64>& all_reduce_id =
- tensorflow::gtl::nullopt);
+ const tensorflow::gtl::optional<int64>& all_reduce_id);
// Returns the group ids of each replica for CrossReplicaSum op.
const std::vector<int64>& replica_group_ids() const {
diff --git a/tensorflow/compiler/xla/service/hlo_parser.cc b/tensorflow/compiler/xla/service/hlo_parser.cc
index e8eaf54949..d71d3c8170 100644
--- a/tensorflow/compiler/xla/service/hlo_parser.cc
+++ b/tensorflow/compiler/xla/service/hlo_parser.cc
@@ -1132,13 +1132,24 @@ bool HloParser::ParseInstruction(HloComputation::Builder* builder,
}
case HloOpcode::kCustomCall: {
optional<string> custom_call_target;
+ optional<Window> window;
+ optional<ConvolutionDimensionNumbers> dnums;
attrs["custom_call_target"] = {/*required=*/true, AttrTy::kString,
&custom_call_target};
+ attrs["window"] = {/*required=*/false, AttrTy::kWindow, &window};
+ attrs["dim_labels"] = {/*required=*/false,
+ AttrTy::kConvolutionDimensionNumbers, &dnums};
if (!ParseOperands(&operands) || !ParseAttributes(attrs)) {
return false;
}
instruction = builder->AddInstruction(HloInstruction::CreateCustomCall(
shape, operands, *custom_call_target));
+ if (window.has_value()) {
+ instruction->set_window(*window);
+ }
+ if (dnums.has_value()) {
+ instruction->set_convolution_dimension_numbers(*dnums);
+ }
break;
}
case HloOpcode::kHostCompute: {
diff --git a/tensorflow/compiler/xla/service/hlo_parser_test.cc b/tensorflow/compiler/xla/service/hlo_parser_test.cc
index 1f0572c576..1c08c51220 100644
--- a/tensorflow/compiler/xla/service/hlo_parser_test.cc
+++ b/tensorflow/compiler/xla/service/hlo_parser_test.cc
@@ -1016,6 +1016,17 @@ ENTRY Iota {
}
)"
+},
+// custom-call with window and dim_labels
+{
+"CustomCallWithWindowAndDimLabels",
+R"(HloModule CustomCallWithWindowAndDimLabels
+
+ENTRY Computation {
+ ROOT r = f32[100]{0} custom-call(), window={size=2x2}, dim_labels=b01f_01io->b01f, custom_call_target="target"
+}
+
+)"
}
});
// clang-format on
diff --git a/tensorflow/compiler/xla/service/hlo_verifier.cc b/tensorflow/compiler/xla/service/hlo_verifier.cc
index c80c1e0e7d..25fa319faf 100644
--- a/tensorflow/compiler/xla/service/hlo_verifier.cc
+++ b/tensorflow/compiler/xla/service/hlo_verifier.cc
@@ -119,7 +119,7 @@ Status CheckIsTokenOperand(const HloInstruction* instruction,
const HloInstruction* token = instruction->operand(operand_no);
if (!ShapeUtil::Equal(token->shape(), ShapeUtil::MakeTokenShape())) {
return InternalError(
- "Expected operand %lld to be token-shaped, actual shape is"
+ "Expected operand %lld to be token-shaped, actual shape is "
"%s:\n%s",
operand_no, ShapeUtil::HumanString(token->shape()).c_str(),
instruction->ToString().c_str());
diff --git a/tensorflow/compiler/xla/service/llvm_ir/BUILD b/tensorflow/compiler/xla/service/llvm_ir/BUILD
index 0573304912..cdd3daf73b 100644
--- a/tensorflow/compiler/xla/service/llvm_ir/BUILD
+++ b/tensorflow/compiler/xla/service/llvm_ir/BUILD
@@ -223,3 +223,22 @@ cc_library(
"@llvm//:core",
],
)
+
+cc_library(
+ name = "buffer_assignment_util",
+ srcs = ["buffer_assignment_util.cc"],
+ hdrs = ["buffer_assignment_util.h"],
+ deps = [
+ "//tensorflow/compiler/xla/service:buffer_assignment",
+ ],
+)
+
+cc_library(
+ name = "math_ops",
+ srcs = ["math_ops.cc"],
+ hdrs = ["math_ops.h"],
+ deps = [
+ ":llvm_util",
+ "@llvm//:core",
+ ],
+)
diff --git a/tensorflow/compiler/xla/service/llvm_ir/alias_analysis_test.cc b/tensorflow/compiler/xla/service/llvm_ir/alias_analysis_test.cc
index 2552ff4a6a..941d940684 100644
--- a/tensorflow/compiler/xla/service/llvm_ir/alias_analysis_test.cc
+++ b/tensorflow/compiler/xla/service/llvm_ir/alias_analysis_test.cc
@@ -58,7 +58,7 @@ ENTRY while3 {
CompileAndVerifyIr(hlo_string, R"(
; CHECK-LABEL: @body(i8* align 4 dereferenceable(4) %retval
; CHECK: %[[add_result:.*]] = fadd fast float %[[fadd_lhs:.*]], %[[fadd_rhs:.*]]
-; CHECK: store float %[[add_result]], float* %[[store_dest:.*]], !alias.scope ![[alias_scope_md_for_store:.*]]
+; CHECK: store float %[[add_result]], float* %[[store_dest:.*]], !alias.scope ![[alias_scope_md_for_store:[0-9]+]]
;
; CHECK-LABEL: @condition(i8* align 1 dereferenceable(1) %fusion, i8* noalias %run_options, i8** noalias %params
; CHECK: %[[cond_state_buf_ptr:.*]] = getelementptr inbounds i8*, i8** %params, i64 0
diff --git a/tensorflow/compiler/xla/service/llvm_ir/buffer_assignment_util.cc b/tensorflow/compiler/xla/service/llvm_ir/buffer_assignment_util.cc
new file mode 100644
index 0000000000..4eb5d9fb47
--- /dev/null
+++ b/tensorflow/compiler/xla/service/llvm_ir/buffer_assignment_util.cc
@@ -0,0 +1,59 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/compiler/xla/service/llvm_ir/buffer_assignment_util.h"
+
+namespace xla {
+namespace llvm_ir {
+static const HloInstruction& InstrForConstantBufferAllocation(
+ const BufferAllocation& allocation) {
+ CHECK(allocation.is_constant());
+ HloInstruction* const_instr = nullptr;
+ for (const auto& buffer_offset_pair : allocation.assigned_buffers()) {
+ const LogicalBuffer* buffer = buffer_offset_pair.first;
+ // BufferAssignment may have assigned non-constant instructions to this
+ // allocation too so we can't CHECK this condition. E.g. for
+ //
+ // while(init = constant, body = identity, cond = ...)
+ //
+ // the LogicalBuffer for the kWhile instruction will have the same
+ // BufferAllocation as the LogicalBuffer for the (init) constant.
+ if (buffer->instruction()->opcode() == HloOpcode::kConstant) {
+ CHECK_EQ(const_instr, nullptr)
+ << const_instr->ToString() << " " << buffer->ToString();
+ const_instr = buffer->instruction();
+ }
+ }
+ CHECK_NE(const_instr, nullptr);
+ return *const_instr;
+}
+
+string ConstantBufferAllocationToGlobalName(
+ const BufferAllocation& allocation) {
+ string instr_name = InstrForConstantBufferAllocation(allocation).name();
+ for (char& c : instr_name) {
+ if (c == '.') {
+ c = '_';
+ }
+ }
+ return tensorflow::strings::StrCat("buffer_for_", instr_name);
+}
+
+const Literal& LiteralForConstantAllocation(
+ const BufferAllocation& allocation) {
+ return InstrForConstantBufferAllocation(allocation).literal();
+}
+} // namespace llvm_ir
+} // namespace xla
diff --git a/tensorflow/compiler/xla/service/llvm_ir/buffer_assignment_util.h b/tensorflow/compiler/xla/service/llvm_ir/buffer_assignment_util.h
new file mode 100644
index 0000000000..bfb6eecb87
--- /dev/null
+++ b/tensorflow/compiler/xla/service/llvm_ir/buffer_assignment_util.h
@@ -0,0 +1,34 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#ifndef TENSORFLOW_COMPILER_XLA_SERVICE_LLVM_IR_BUFFER_ASSIGNMENT_UTIL_H_
+#define TENSORFLOW_COMPILER_XLA_SERVICE_LLVM_IR_BUFFER_ASSIGNMENT_UTIL_H_
+
+#include "tensorflow/compiler/xla/service/buffer_assignment.h"
+
+namespace xla {
+namespace llvm_ir {
+// In XLA:GPU we map constant buffer allocations to globals in the generated
+// LLVM IR. This function gives us the name of the global variable a constant
+// buffer is mapped to. Not used on XLA:CPU.
+string ConstantBufferAllocationToGlobalName(const BufferAllocation& allocation);
+
+// Returns the Literal corresponding to `allocation`, which must be a constant
+// allocation.
+const Literal& LiteralForConstantAllocation(const BufferAllocation& allocation);
+} // namespace llvm_ir
+} // namespace xla
+
+#endif // TENSORFLOW_COMPILER_XLA_SERVICE_LLVM_IR_BUFFER_ASSIGNMENT_UTIL_H_
diff --git a/tensorflow/compiler/xla/service/llvm_ir/dynamic_update_slice_util.cc b/tensorflow/compiler/xla/service/llvm_ir/dynamic_update_slice_util.cc
index 1bd73fc793..27fbb11e2e 100644
--- a/tensorflow/compiler/xla/service/llvm_ir/dynamic_update_slice_util.cc
+++ b/tensorflow/compiler/xla/service/llvm_ir/dynamic_update_slice_util.cc
@@ -56,10 +56,6 @@ static Status EmitDynamicUpdateSliceInPlaceImpl(
// Clamp the start index so that the update region fits in the operand.
// start_index = clamp(start_index, 0, output_dim_size - update_dim_size)
-
- // TODO(b/74360564): This is implementation defined behavior, but is
- // currently respected by all implementations. Change this if we ever decide
- // to officially document different behavior.
llvm::Value* max_bound = b->CreateSub(output_dim_size, update_dim_size);
llvm::Value* zero = llvm::ConstantInt::get(start_index[i]->getType(), 0);
start_index[i] =
diff --git a/tensorflow/compiler/xla/service/llvm_ir/ir_array.cc b/tensorflow/compiler/xla/service/llvm_ir/ir_array.cc
index 7a9170f379..2b6caee6aa 100644
--- a/tensorflow/compiler/xla/service/llvm_ir/ir_array.cc
+++ b/tensorflow/compiler/xla/service/llvm_ir/ir_array.cc
@@ -327,6 +327,7 @@ llvm::Value* IrArray::Index::Linearize(
llvm::IRBuilder<>* builder) const {
// Each dimension is multiplied by the product of the sizes of all
// earlier dimensions and added to the accumulator logical_linear_index.
+ CHECK_EQ(size(), dimensions.size());
llvm::Value* logical_linear_index = GetConstantWithIndexType(0);
int64 multiplier = 1;
for (ssize_t i = size() - 1; i >= 0; --i) {
diff --git a/tensorflow/compiler/xla/service/llvm_ir/llvm_util.cc b/tensorflow/compiler/xla/service/llvm_ir/llvm_util.cc
index e4f65bd427..e6126881af 100644
--- a/tensorflow/compiler/xla/service/llvm_ir/llvm_util.cc
+++ b/tensorflow/compiler/xla/service/llvm_ir/llvm_util.cc
@@ -657,5 +657,56 @@ void InitializeLLVMCommandLineOptions(const HloModuleConfig& config) {
}
}
+std::pair<llvm::Value*, llvm::Value*> UMulLowHigh32(llvm::IRBuilder<>* b,
+ llvm::Value* src0,
+ llvm::Value* src1) {
+ CHECK_EQ(src0->getType()->getPrimitiveSizeInBits(), 32);
+ CHECK_EQ(src1->getType()->getPrimitiveSizeInBits(), 32);
+ llvm::Type* int64_ty = b->getInt64Ty();
+ src0 = b->CreateZExt(src0, int64_ty);
+ src1 = b->CreateZExt(src1, int64_ty);
+ return SplitInt64ToInt32s(b, b->CreateMul(src0, src1));
+}
+
+std::pair<llvm::Value*, llvm::Value*> SplitInt64ToInt32s(
+ llvm::IRBuilder<>* b, llvm::Value* value_64bits) {
+ CHECK_EQ(value_64bits->getType()->getPrimitiveSizeInBits(), 64);
+ llvm::Type* int32_ty = b->getInt32Ty();
+ llvm::Value* low_32bits = b->CreateTrunc(value_64bits, int32_ty);
+ llvm::Value* high_32bits =
+ b->CreateTrunc(b->CreateLShr(value_64bits, 32), int32_ty);
+ return std::make_pair(low_32bits, high_32bits);
+}
+
+llvm::GlobalVariable* GetOrCreateVariableForPhiloxRngState(
+ llvm::Module* module, llvm::IRBuilder<>* b) {
+ static const char* kPhiloxRngStateVariableName = "philox_rng_state";
+ llvm::GlobalVariable* state_ptr =
+ module->getNamedGlobal(kPhiloxRngStateVariableName);
+ if (!state_ptr) {
+ state_ptr = new llvm::GlobalVariable(
+ /*M=*/*module,
+ /*Ty=*/b->getInt64Ty(),
+ /*isConstant=*/false,
+ /*Linkage=*/llvm::GlobalValue::PrivateLinkage,
+ /*Initializer=*/b->getInt64(0),
+ /*Name=*/kPhiloxRngStateVariableName);
+ }
+ return state_ptr;
+}
+
+void IncrementVariableForPhiloxRngState(int64 value, llvm::Module* module,
+ llvm::IRBuilder<>* builder) {
+ llvm::GlobalVariable* state_ptr =
+ GetOrCreateVariableForPhiloxRngState(module, builder);
+ llvm::Value* state_value_old = builder->CreateLoad(state_ptr, "load_state");
+ // If the 64-bit value overflows, we use the wraparound value. This should
+ // be fine in practice as we only add one to the value each time when a RNG is
+ // executed.
+ llvm::Value* state_value_new = builder->CreateAdd(
+ state_value_old, builder->getInt64(value), "inc_state");
+ builder->CreateStore(state_value_new, state_ptr);
+}
+
} // namespace llvm_ir
} // namespace xla
diff --git a/tensorflow/compiler/xla/service/llvm_ir/llvm_util.h b/tensorflow/compiler/xla/service/llvm_ir/llvm_util.h
index d8746ffe01..0958398534 100644
--- a/tensorflow/compiler/xla/service/llvm_ir/llvm_util.h
+++ b/tensorflow/compiler/xla/service/llvm_ir/llvm_util.h
@@ -292,6 +292,27 @@ llvm::Function* CreateFunction(llvm::FunctionType* function_type,
// don't start with xla_ to LLVM.
void InitializeLLVMCommandLineOptions(const HloModuleConfig& config);
+// Zero-extends two 32-bit values to 64 bits, multiplies them, and returns the
+// result as a pair of (low 32 bits, high 32 bits).
+std::pair<llvm::Value*, llvm::Value*> UMulLowHigh32(llvm::IRBuilder<>* b,
+ llvm::Value* src0,
+ llvm::Value* src1);
+// Splits the 64-bit integer value into its high and low 32 bits.
+std::pair<llvm::Value*, llvm::Value*> SplitInt64ToInt32s(
+ llvm::IRBuilder<>* b, llvm::Value* value_64bits);
+
+// Checks whether a global variable is already created to represent a
+// state passed between RNG calls implemented with Philox algorithm. If not,
+// creates such a variable. Returns the global variable.
+llvm::GlobalVariable* GetOrCreateVariableForPhiloxRngState(
+ llvm::Module* module, llvm::IRBuilder<>* b);
+
+// Adds a value to the global state variable each time when a RNG hlo is
+// executed. The value of this global state variable is added to the seed
+// of the Philox RNG algorithm so that calling the same RNG Hlo multiple times
+// should rarely produce the same result.
+void IncrementVariableForPhiloxRngState(int64 value, llvm::Module* module,
+ llvm::IRBuilder<>* b);
} // namespace llvm_ir
} // namespace xla
diff --git a/tensorflow/compiler/xla/service/llvm_ir/math_ops.cc b/tensorflow/compiler/xla/service/llvm_ir/math_ops.cc
new file mode 100644
index 0000000000..0e115cdabf
--- /dev/null
+++ b/tensorflow/compiler/xla/service/llvm_ir/math_ops.cc
@@ -0,0 +1,59 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/compiler/xla/service/llvm_ir/math_ops.h"
+#include "tensorflow/compiler/xla/service/llvm_ir/llvm_util.h"
+
+namespace xla {
+namespace llvm_ir {
+
+llvm::Value* EmitFastTanh(llvm::IRBuilder<>* b, llvm::Value* input) {
+ llvm::Type* type = input->getType();
+
+ // Clamp the input to [-9, 9].
+ llvm::Value* input_clamped = llvm_ir::EmitFloatMin(
+ llvm_ir::EmitFloatMax(input, llvm::ConstantFP::get(type, -9.0), b),
+ llvm::ConstantFP::get(type, 9.0), b);
+
+ static constexpr std::array<float, 7> numerator_coeffs{
+ -2.76076847742355e-16f, 2.00018790482477e-13f, -8.60467152213735e-11f,
+ 5.12229709037114e-08f, 1.48572235717979e-05f, 6.37261928875436e-04f,
+ 4.89352455891786e-03f};
+
+ static constexpr std::array<float, 4> denominator_coeffs{
+ 1.19825839466702e-06f, 1.18534705686654e-04f, 2.26843463243900e-03f,
+ 4.89352518554385e-03f};
+
+ llvm::Value* input_squared = b->CreateFMul(input_clamped, input_clamped);
+ llvm::Value* numerator = llvm::ConstantFP::get(type, numerator_coeffs[0]);
+ for (int i = 1; i < numerator_coeffs.size(); i++) {
+ numerator = b->CreateFAdd(b->CreateFMul(input_squared, numerator),
+ llvm::ConstantFP::get(type, numerator_coeffs[i]));
+ }
+
+ numerator = b->CreateFMul(input_clamped, numerator);
+
+ llvm::Value* denominator = llvm::ConstantFP::get(type, denominator_coeffs[0]);
+ for (int i = 1; i < denominator_coeffs.size(); i++) {
+ denominator =
+ b->CreateFAdd(b->CreateFMul(input_squared, denominator),
+ llvm::ConstantFP::get(type, denominator_coeffs[i]));
+ }
+
+ return b->CreateFDiv(numerator, denominator);
+}
+
+} // namespace llvm_ir
+} // namespace xla
diff --git a/tensorflow/compiler/xla/service/pool_test.cc b/tensorflow/compiler/xla/service/llvm_ir/math_ops.h
index 8c4fe258e3..6c8bc3a076 100644
--- a/tensorflow/compiler/xla/service/pool_test.cc
+++ b/tensorflow/compiler/xla/service/llvm_ir/math_ops.h
@@ -1,4 +1,4 @@
-/* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,28 +13,20 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
-#include "tensorflow/compiler/xla/service/pool.h"
+#ifndef TENSORFLOW_COMPILER_XLA_SERVICE_LLVM_IR_MATH_OPS_H_
+#define TENSORFLOW_COMPILER_XLA_SERVICE_LLVM_IR_MATH_OPS_H_
-#include "tensorflow/compiler/xla/test_helpers.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/Value.h"
namespace xla {
-namespace {
+namespace llvm_ir {
-using PoolTest = ::testing::Test;
+// Emits an approximation of tanh. The implementation uses the same rational
+// interpolant as implemented in Eigen3.
+llvm::Value* EmitFastTanh(llvm::IRBuilder<>* b, llvm::Value* input);
-TEST_F(PoolTest, Test) {
- Pool<int> pool;
-
- {
- auto ptr = pool.Allocate();
- EXPECT_NE(nullptr, ptr.get());
- *ptr = 5;
- }
-
- auto ptr = pool.Allocate();
- EXPECT_NE(nullptr, ptr.get());
- EXPECT_EQ(5, *ptr);
-}
-
-} // namespace
+} // namespace llvm_ir
} // namespace xla
+
+#endif // TENSORFLOW_COMPILER_XLA_SERVICE_LLVM_IR_MATH_OPS_H_
diff --git a/tensorflow/compiler/xla/service/llvm_ir/sort_util.cc b/tensorflow/compiler/xla/service/llvm_ir/sort_util.cc
index 6f261c32f4..e546f5cc4a 100644
--- a/tensorflow/compiler/xla/service/llvm_ir/sort_util.cc
+++ b/tensorflow/compiler/xla/service/llvm_ir/sort_util.cc
@@ -30,6 +30,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/lib/core/stringpiece.h"
+#include "tensorflow/core/lib/gtl/optional.h"
#include "tensorflow/core/platform/types.h"
namespace xla {
@@ -38,19 +39,18 @@ namespace llvm_ir {
namespace {
// Adds the inner comparison loop where we compare elements pointed to by
// 'keys_index' and 'compare_keys_index'.
-void EmitCompareLoop(int64 dimension_to_sort,
- const llvm_ir::IrArray::Index& keys_index,
- const llvm_ir::IrArray::Index& compare_keys_index,
- const llvm_ir::IrArray& keys_array, llvm::IRBuilder<>* b) {
- // TODO(b/26783907): parallelize this loop.
-
+void EmitCompareLoop(int64 dimension_to_sort, const IrArray::Index& keys_index,
+ const IrArray::Index& compare_keys_index,
+ const IrArray& keys_array,
+ const tensorflow::gtl::optional<IrArray>& values_array,
+ llvm::IRBuilder<>* b) {
// if (is_smaller_index &&
// compare_keys[dimension_to_sort] < dimension_to_sort_bound)
llvm::Value* is_smaller_index = b->CreateICmpSLT(
keys_index[dimension_to_sort], compare_keys_index[dimension_to_sort]);
int64 dimension_to_sort_bound =
keys_array.GetShape().dimensions(dimension_to_sort);
- auto if_data = llvm_ir::EmitIfThenElse(
+ auto if_data = EmitIfThenElse(
b->CreateAnd(is_smaller_index,
b->CreateICmpSLT(compare_keys_index[dimension_to_sort],
keys_index.GetConstantWithIndexType(
@@ -63,30 +63,36 @@ void EmitCompareLoop(int64 dimension_to_sort,
auto comparison =
primitive_util::IsFloatingPointType(key_type)
// TODO(b/26783907): Figure out how to handle NaNs.
- ? b->CreateFCmp(llvm::FCmpInst::FCMP_ULT, key1, key2)
+ ? b->CreateFCmp(llvm::FCmpInst::FCMP_ULT, key2, key1)
: b->CreateICmp(primitive_util::IsSignedIntegralType(key_type)
? llvm::ICmpInst::ICMP_SLT
: llvm::ICmpInst::ICMP_ULT,
- key1, key2);
- auto min_key = b->CreateSelect(comparison, key1, key2);
- auto max_key = b->CreateSelect(comparison, key2, key1);
- keys_array.EmitWriteArrayElement(keys_index, min_key, b);
- keys_array.EmitWriteArrayElement(compare_keys_index, max_key, b);
+ key2, key1);
+ // If key2 < key1
+ auto if_smaller_data =
+ EmitIfThenElse(comparison, "is_smaller_than", b, /*emit_else=*/false);
+ SetToFirstInsertPoint(if_smaller_data.true_block, b);
+ // Swap key1 with key2.
+ keys_array.EmitWriteArrayElement(keys_index, key2, b);
+ keys_array.EmitWriteArrayElement(compare_keys_index, key1, b);
+ if (values_array.has_value()) {
+ // Also swap the values.
+ auto value1 = values_array.value().EmitReadArrayElement(keys_index, b);
+ auto value2 =
+ values_array.value().EmitReadArrayElement(compare_keys_index, b);
+ values_array.value().EmitWriteArrayElement(keys_index, value2, b);
+ values_array.value().EmitWriteArrayElement(compare_keys_index, value1, b);
+ }
}
} // namespace
Status EmitSortInPlace(int64 dimension_to_sort, const IrArray& keys_array,
+ const tensorflow::gtl::optional<IrArray>& values_array,
tensorflow::StringPiece name, llvm::Value* xor_mask,
llvm::IRBuilder<>* b,
const gpu::LaunchDimensions* launch_dimensions) {
const Shape& keys_shape = keys_array.GetShape();
- // TODO(b/26783907): This case can probably be avoided with the Algebraic
- // Simplifier.
- if (ShapeUtil::IsScalar(keys_shape)) {
- return Status::OK();
- }
-
// Create loop nests which loop through the operand dimensions. The sort
// dimension is handled in the innermost loop which performs the sorting.
ForLoopNest loop_nest(name, b);
@@ -131,7 +137,7 @@ Status EmitSortInPlace(int64 dimension_to_sort, const IrArray& keys_array,
compare_keys_index[dimension_to_sort] =
b->CreateXor(compare_index[0], xor_mask);
EmitCompareLoop(dimension_to_sort, keys_index, compare_keys_index,
- keys_array, b);
+ keys_array, values_array, b);
return Status::OK();
};
if (launch_dimensions != nullptr) {
diff --git a/tensorflow/compiler/xla/service/llvm_ir/sort_util.h b/tensorflow/compiler/xla/service/llvm_ir/sort_util.h
index e75f9b08fb..8458744c6b 100644
--- a/tensorflow/compiler/xla/service/llvm_ir/sort_util.h
+++ b/tensorflow/compiler/xla/service/llvm_ir/sort_util.h
@@ -21,6 +21,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/service/llvm_ir/ir_array.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/lib/core/stringpiece.h"
+#include "tensorflow/core/lib/gtl/optional.h"
#include "tensorflow/core/platform/types.h"
namespace xla {
@@ -30,6 +31,7 @@ namespace llvm_ir {
// implements the inner loop of BitonicSort. If 'launch_dimensions' is nullptr,
// the inner compare loop will not be parallelized.
Status EmitSortInPlace(int64 dimension_to_sort, const IrArray& keys_array,
+ const tensorflow::gtl::optional<IrArray>& values_array,
tensorflow::StringPiece name, llvm::Value* xor_mask,
llvm::IRBuilder<>* b,
const gpu::LaunchDimensions* launch_dimensions);
diff --git a/tensorflow/compiler/xla/service/local_service.cc b/tensorflow/compiler/xla/service/local_service.cc
index 53efc30c36..5e02096ee5 100644
--- a/tensorflow/compiler/xla/service/local_service.cc
+++ b/tensorflow/compiler/xla/service/local_service.cc
@@ -20,6 +20,7 @@ limitations under the License.
#include <vector>
#include "tensorflow/compiler/xla/client/executable_build_options.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/execution_options_util.h"
#include "tensorflow/compiler/xla/ptr_util.h"
#include "tensorflow/compiler/xla/service/backend.h"
diff --git a/tensorflow/compiler/xla/service/local_service.h b/tensorflow/compiler/xla/service/local_service.h
index 39d6734c3f..8f707ea904 100644
--- a/tensorflow/compiler/xla/service/local_service.h
+++ b/tensorflow/compiler/xla/service/local_service.h
@@ -19,7 +19,7 @@ limitations under the License.
#include <memory>
#include "tensorflow/compiler/xla/client/executable_build_options.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/service/backend.h"
#include "tensorflow/compiler/xla/service/compiler.h"
#include "tensorflow/compiler/xla/service/device_memory_allocator.h"
diff --git a/tensorflow/compiler/xla/service/pool.h b/tensorflow/compiler/xla/service/pool.h
deleted file mode 100644
index 8e710ebb6d..0000000000
--- a/tensorflow/compiler/xla/service/pool.h
+++ /dev/null
@@ -1,84 +0,0 @@
-/* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-==============================================================================*/
-
-#ifndef TENSORFLOW_COMPILER_XLA_POOL_H_
-#define TENSORFLOW_COMPILER_XLA_POOL_H_
-
-#include <functional>
-#include <vector>
-
-#include "tensorflow/compiler/xla/ptr_util.h"
-#include "tensorflow/core/platform/mutex.h"
-
-namespace xla {
-
-// Pool of values, which are created as needed and destroyed when the `Pool` is
-// destroyed
-template <typename T>
-class Pool {
- public:
- struct Deleter {
- void operator()(T* ptr) { pool->Deallocate(ptr); }
-
- Pool<T>* pool;
- };
-
- // A pointer to a taken element of a `Pool` which returns it to the pool on
- // destruction
- using SmartPtr = std::unique_ptr<T, Deleter>;
-
- // Constructs a `Pool` with given factory function, which need not be
- // thread-safe.
- explicit Pool(std::function<std::unique_ptr<T>()> factory)
- : factory_(factory) {}
-
- explicit Pool() : Pool([]() { return MakeUnique<T>(); }) {}
-
- // Returns a pointer to a value in the pool, creating a new value if none is
- // free. The returned smart pointer returns the element to the pool on
- // destruction.
- //
- // This method is thread-safe.
- SmartPtr Allocate() {
- tensorflow::mutex_lock lock(mu_);
- T* ptr;
- if (!xs_.empty()) {
- ptr = std::move(xs_.back()).release();
- xs_.pop_back();
- } else {
- ptr = factory_().release();
- }
- Deleter del = {this};
- return std::unique_ptr<T, Deleter>(ptr, del);
- }
-
- private:
- // Puts a pointer to a value back into the pool, leaving it free for future
- // use.
- //
- // This method is thread-safe.
- void Deallocate(T* ptr) {
- tensorflow::mutex_lock lock(mu_);
- xs_.push_back(std::unique_ptr<T>(ptr));
- }
-
- const std::function<std::unique_ptr<T>()> factory_ GUARDED_BY(mu_);
- std::vector<std::unique_ptr<T>> xs_ GUARDED_BY(mu_);
- tensorflow::mutex mu_;
-};
-
-} // namespace xla
-
-#endif // TENSORFLOW_COMPILER_XLA_POOL_H_
diff --git a/tensorflow/compiler/xla/service/service.cc b/tensorflow/compiler/xla/service/service.cc
index 636013cbb5..ce070bc5b6 100644
--- a/tensorflow/compiler/xla/service/service.cc
+++ b/tensorflow/compiler/xla/service/service.cc
@@ -37,6 +37,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/service/hlo_proto_util.h"
#include "tensorflow/compiler/xla/service/platform_util.h"
#include "tensorflow/compiler/xla/service/source_map_util.h"
+#include "tensorflow/compiler/xla/service/stream_pool.h"
#include "tensorflow/compiler/xla/service/transfer_manager.h"
#include "tensorflow/compiler/xla/shape_layout.h"
#include "tensorflow/compiler/xla/shape_util.h"
@@ -376,7 +377,7 @@ Service::ExecuteParallelAndRegisterResult(
ExecutionProfile* profile) {
// Streams where the computation are launched, so we can wait on the streams
// to complete.
- std::vector<Pool<se::Stream>::SmartPtr> streams;
+ std::vector<StreamPool::Ptr> streams;
std::vector<std::unique_ptr<se::Timer>> timers;
// Global data handles for the computation results, one for each computation.
@@ -403,7 +404,7 @@ Service::ExecuteParallelAndRegisterResult(
CHECK_EQ(replicas.size(), arguments[i].size());
std::vector<ScopedShapedBuffer> result_buffers;
for (int64 replica = 0; replica < replicas.size(); ++replica) {
- TF_ASSIGN_OR_RETURN(Pool<se::Stream>::SmartPtr stream,
+ TF_ASSIGN_OR_RETURN(StreamPool::Ptr stream,
backend->BorrowStream(replicas[replica]));
streams.push_back(std::move(stream));
@@ -515,13 +516,13 @@ StatusOr<GlobalDataHandle> Service::ExecuteAndRegisterResult(
arguments,
Backend* backend, const string& result_tag, ExecutionProfile* profile) {
// Set up streams.
- std::vector<Pool<se::Stream>::SmartPtr> streams;
+ std::vector<StreamPool::Ptr> streams;
TF_ASSIGN_OR_RETURN(auto replicas,
Replicas(*backend, SingleComputationDeviceHandle()));
TF_RET_CHECK(!replicas.empty());
for (se::StreamExecutor* executor : replicas) {
- TF_ASSIGN_OR_RETURN(Pool<se::Stream>::SmartPtr stream,
+ TF_ASSIGN_OR_RETURN(StreamPool::Ptr stream,
backend->BorrowStream(executor));
streams.push_back(std::move(stream));
}
@@ -533,7 +534,7 @@ StatusOr<GlobalDataHandle> Service::ExecuteAndRegisterResult(
// Set up run options.
std::vector<ServiceExecutableRunOptions> run_options;
- for (const Pool<se::Stream>::SmartPtr& stream : streams) {
+ for (const StreamPool::Ptr& stream : streams) {
ExecutableRunOptions options;
options.set_stream(stream.get());
options.set_device_ordinal(stream->parent()->device_ordinal());
diff --git a/tensorflow/compiler/xla/service/service_executable_run_options.h b/tensorflow/compiler/xla/service/service_executable_run_options.h
index 7f3910cdb0..dbfed628bf 100644
--- a/tensorflow/compiler/xla/service/service_executable_run_options.h
+++ b/tensorflow/compiler/xla/service/service_executable_run_options.h
@@ -17,7 +17,7 @@ limitations under the License.
#define TENSORFLOW_COMPILER_XLA_SERVICE_SERVICE_EXECUTABLE_RUN_OPTIONS_H_
#include "tensorflow/compiler/xla/executable_run_options.h"
-#include "tensorflow/compiler/xla/service/pool.h"
+#include "tensorflow/compiler/xla/service/stream_pool.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/stream_executor/stream_executor.h"
@@ -27,8 +27,7 @@ namespace xla {
// data, now only a stream cache for GPU backend.
class ServiceExecutableRunOptions {
public:
- using StreamBorrower =
- std::function<StatusOr<Pool<se::Stream>::SmartPtr>(int)>;
+ using StreamBorrower = std::function<StatusOr<StreamPool::Ptr>(int)>;
ServiceExecutableRunOptions()
: ServiceExecutableRunOptions(ExecutableRunOptions()) {}
@@ -51,7 +50,7 @@ class ServiceExecutableRunOptions {
// Borrows a stream and returns a smart pointer which returns the stream on
// destruction.
- StatusOr<Pool<se::Stream>::SmartPtr> BorrowStream(int device_ordinal) const {
+ StatusOr<StreamPool::Ptr> BorrowStream(int device_ordinal) const {
return borrow_stream_
? borrow_stream_(device_ordinal)
: Status(tensorflow::error::UNIMPLEMENTED, "No stream cache");
diff --git a/tensorflow/compiler/xla/service/stream_pool.cc b/tensorflow/compiler/xla/service/stream_pool.cc
new file mode 100644
index 0000000000..92bb21b816
--- /dev/null
+++ b/tensorflow/compiler/xla/service/stream_pool.cc
@@ -0,0 +1,56 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/compiler/xla/service/stream_pool.h"
+
+#include "tensorflow/compiler/xla/ptr_util.h"
+
+namespace xla {
+
+StreamPool::Ptr StreamPool::BorrowStream(se::StreamExecutor* executor) {
+ std::unique_ptr<se::Stream> stream;
+ {
+ tensorflow::mutex_lock lock(mu_);
+ if (!streams_.empty()) {
+ // Re-use an existing stream from the pool.
+ stream = std::move(streams_.back());
+ streams_.pop_back();
+ }
+ }
+
+ if (!stream) {
+ // Create a new stream.
+ stream = MakeUnique<se::Stream>(executor);
+ stream->Init();
+ }
+
+ // Return the stream wrapped in Ptr, which has our special deleter semantics.
+ PtrDeleter deleter = {this};
+ return Ptr(stream.release(), deleter);
+}
+
+void StreamPool::ReturnStream(se::Stream* stream) {
+ if (stream->ok()) {
+ tensorflow::mutex_lock lock(mu_);
+ streams_.emplace_back(stream);
+ } else {
+ // If the stream has encountered any errors, all subsequent
+ // operations on it will fail. So just delete the stream, and rely
+ // on new streams to be created in the future.
+ delete stream;
+ }
+}
+
+} // namespace xla
diff --git a/tensorflow/compiler/xla/service/stream_pool.h b/tensorflow/compiler/xla/service/stream_pool.h
new file mode 100644
index 0000000000..7221d323a6
--- /dev/null
+++ b/tensorflow/compiler/xla/service/stream_pool.h
@@ -0,0 +1,64 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#ifndef TENSORFLOW_COMPILER_XLA_SERVICE_STREAM_POOL_H_
+#define TENSORFLOW_COMPILER_XLA_SERVICE_STREAM_POOL_H_
+
+#include <memory>
+#include <vector>
+
+#include "tensorflow/compiler/xla/types.h"
+#include "tensorflow/core/platform/mutex.h"
+#include "tensorflow/core/platform/stream_executor_no_cuda.h"
+
+namespace xla {
+
+// Pool of stream_executor::Streams, which are created as needed and
+// destroyed when the pool is destroyed.
+class StreamPool {
+ public:
+ struct PtrDeleter {
+ void operator()(se::Stream* stream) { pool->ReturnStream(stream); }
+ StreamPool* pool;
+ };
+
+ // Stream pointer type returned by BorrowStream, which returns the
+ // stream to the pool on destruction.
+ using Ptr = std::unique_ptr<se::Stream, PtrDeleter>;
+
+ StreamPool() {}
+
+ // Returns a pointer to a stream in the pool, creating a new stream
+ // if none are available in the pool. The returned smart pointer
+ // returns the stream to the pool on destruction.
+ //
+ // This method is thread-safe.
+ Ptr BorrowStream(se::StreamExecutor* executor);
+
+ private:
+ // Puts a pointer to a stream back into the pool, leaving it free
+ // for future use. Streams that have previously encountered errors
+ // are deleted, and not returned to the pool.
+ //
+ // This method is thread-safe.
+ void ReturnStream(se::Stream* stream);
+
+ tensorflow::mutex mu_;
+ std::vector<std::unique_ptr<se::Stream>> streams_ GUARDED_BY(mu_);
+};
+
+} // namespace xla
+
+#endif // TENSORFLOW_COMPILER_XLA_SERVICE_STREAM_POOL_H_
diff --git a/tensorflow/compiler/xla/service/stream_pool_test.cc b/tensorflow/compiler/xla/service/stream_pool_test.cc
new file mode 100644
index 0000000000..aaf5c37b0d
--- /dev/null
+++ b/tensorflow/compiler/xla/service/stream_pool_test.cc
@@ -0,0 +1,136 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/compiler/xla/service/stream_pool.h"
+
+#include <memory>
+
+#include "tensorflow/compiler/xla/test_helpers.h"
+#include "tensorflow/core/platform/stream_executor_no_cuda.h"
+
+namespace xla {
+namespace {
+
+class StreamPoolTest : public ::testing::Test {
+ protected:
+ std::unique_ptr<se::StreamExecutor> NewStreamExecutor() {
+ se::Platform* platform =
+ se::MultiPlatformManager::PlatformWithName("Host").ConsumeValueOrDie();
+ se::StreamExecutorConfig config(/*ordinal=*/0);
+ return platform->GetUncachedExecutor(config).ConsumeValueOrDie();
+ }
+};
+
+TEST_F(StreamPoolTest, EmptyPool) { StreamPool pool; }
+
+TEST_F(StreamPoolTest, OneStreamPool) {
+ std::unique_ptr<se::StreamExecutor> executor = NewStreamExecutor();
+ StreamPool pool;
+
+ // Borrow and return a stream.
+ StreamPool::Ptr stream1 = pool.BorrowStream(executor.get());
+ se::Stream* stream1_ptr = stream1.get();
+ EXPECT_TRUE(stream1->ok());
+ stream1 = nullptr;
+
+ // Borrow and return another stream.
+ StreamPool::Ptr stream2 = pool.BorrowStream(executor.get());
+ se::Stream* stream2_ptr = stream2.get();
+ EXPECT_TRUE(stream2->ok());
+ stream2 = nullptr;
+
+ // The underlying streams should be the same, since stream1 was the
+ // only stream available in the pool when stream2 was borrowed.
+ EXPECT_EQ(stream1_ptr, stream2_ptr);
+}
+
+TEST_F(StreamPoolTest, TwoStreamPool) {
+ std::unique_ptr<se::StreamExecutor> executor = NewStreamExecutor();
+ StreamPool pool;
+
+ // Borrow two streams.
+ StreamPool::Ptr stream1 = pool.BorrowStream(executor.get());
+ se::Stream* stream1_ptr = stream1.get();
+ EXPECT_TRUE(stream1->ok());
+ StreamPool::Ptr stream2 = pool.BorrowStream(executor.get());
+ se::Stream* stream2_ptr = stream2.get();
+ EXPECT_TRUE(stream2->ok());
+
+ // The underlying streams should be different, since we haven't
+ // returned either of them yet.
+ EXPECT_NE(stream1_ptr, stream2_ptr);
+
+ // Return stream1 and borrow stream3.
+ stream1 = nullptr;
+ StreamPool::Ptr stream3 = pool.BorrowStream(executor.get());
+ se::Stream* stream3_ptr = stream3.get();
+ EXPECT_TRUE(stream3->ok());
+
+ // stream1 and stream3 should be the same.
+ EXPECT_EQ(stream1_ptr, stream3_ptr);
+ EXPECT_NE(stream2_ptr, stream3_ptr);
+
+ // Return stream2, and borrow stream4.
+ stream2 = nullptr;
+ StreamPool::Ptr stream4 = pool.BorrowStream(executor.get());
+ se::Stream* stream4_ptr = stream4.get();
+ EXPECT_TRUE(stream4->ok());
+
+ // Stream2 and stream4 should be the same.
+ EXPECT_EQ(stream2_ptr, stream4_ptr);
+ EXPECT_NE(stream3_ptr, stream4_ptr);
+}
+
+TEST_F(StreamPoolTest, BadStreamDiscarded) {
+ std::unique_ptr<se::StreamExecutor> executor = NewStreamExecutor();
+ StreamPool pool;
+
+ // Borrow a stream.
+ StreamPool::Ptr stream1 = pool.BorrowStream(executor.get());
+ EXPECT_TRUE(stream1->ok());
+
+ // Force an error on the stream; here we call a method that requires
+ // DNN support, which we know the Host platform doesn't support.
+ stream1->ThenDepthConcatenate({}, {}, nullptr);
+ EXPECT_FALSE(stream1->ok());
+
+ // Return stream1 and borrow stream2.
+ stream1 = nullptr;
+ StreamPool::Ptr stream2 = pool.BorrowStream(executor.get());
+ se::Stream* stream2_ptr = stream2.get();
+ EXPECT_TRUE(stream2->ok());
+
+ // The underlying streams should be different. They would have been
+ // the same, but since we forced an error on stream1, it cannot be
+ // put back into the pool. Sadly we can't just check:
+ // EXPECT_NE(stream1_ptr, stream2_ptr);
+ //
+ // The above should hold logically, but it may fail if the new
+ // stream instance allocated for stream2 happens to reside in the
+ // same memory address as stream1, which has been deleted.
+ //
+ // The check that stream2->ok() serves as a good-enough check.
+
+ // Return stream2 and borrow stream3. The previous error on stream1
+ // has no effect on these streams, and they are the same.
+ stream2 = nullptr;
+ StreamPool::Ptr stream3 = pool.BorrowStream(executor.get());
+ se::Stream* stream3_ptr = stream3.get();
+ EXPECT_TRUE(stream3->ok());
+ EXPECT_EQ(stream2_ptr, stream3_ptr);
+}
+
+} // namespace
+} // namespace xla
diff --git a/tensorflow/compiler/xla/service/transpose_folding_test.cc b/tensorflow/compiler/xla/service/transpose_folding_test.cc
index 7051a4cf51..58f767e913 100644
--- a/tensorflow/compiler/xla/service/transpose_folding_test.cc
+++ b/tensorflow/compiler/xla/service/transpose_folding_test.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include <unordered_set>
#include <vector>
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/service/gpu/ir_emission_utils.h"
#include "tensorflow/compiler/xla/service/hlo_computation.h"
diff --git a/tensorflow/compiler/xla/service/tuple_points_to_analysis.cc b/tensorflow/compiler/xla/service/tuple_points_to_analysis.cc
index 990dfc410c..0effdc80a4 100644
--- a/tensorflow/compiler/xla/service/tuple_points_to_analysis.cc
+++ b/tensorflow/compiler/xla/service/tuple_points_to_analysis.cc
@@ -718,6 +718,7 @@ bool TuplePointsToAnalysis::HasUniqueFusedUseOfOperandAt(
// root at operand 0 or 1. Or...
// (4) The 'user' of 'operand' is DynamicUpdateSlice or While at operand index
// 0.
+// (5) The 'user' of 'operand' is Sort, and it is the only user.
//
// (2) and (3) can only be determined if points-to analysis is available.
bool TuplePointsToAnalysis::CanShareOperandBufferWithUser(
@@ -783,6 +784,21 @@ bool TuplePointsToAnalysis::CanShareOperandBufferWithUser(
std::vector<int64> operand_indices = user->OperandIndices(operand);
return operand_indices.size() == 1 && operand_indices[0] == 0;
}
+ if (user->opcode() == HloOpcode::kSort) {
+ // Only valid if there are no other users.
+ if (operand->users().size() != 1) {
+ return false;
+ }
+ // If we only sort keys, the output of sort is not a tuple, so we can always
+ // share the buffer.
+ if (user->operand_count() == 1) {
+ return true;
+ }
+ CHECK(!user_index.empty());
+ // Only share with the right tuple element buffer.
+ std::vector<int64> operand_indices = user->OperandIndices(operand);
+ return operand_indices.size() == 1 && user_index[0] == operand_indices[0];
+ }
if (user->opcode() == HloOpcode::kCall) {
// TODO(b/62548313): Remove when buffer assignment is module scoped and
// does not assign buffers to calls.
diff --git a/tensorflow/compiler/xla/service/tuple_points_to_analysis_test.cc b/tensorflow/compiler/xla/service/tuple_points_to_analysis_test.cc
index 0ac8df4271..2e5f646804 100644
--- a/tensorflow/compiler/xla/service/tuple_points_to_analysis_test.cc
+++ b/tensorflow/compiler/xla/service/tuple_points_to_analysis_test.cc
@@ -1012,6 +1012,48 @@ TEST_F(CanShareOperandBufferWithUserTest, DynamicUpdateSliceCanShare) {
points_to_analysis_->CanShareOperandBufferWithUser(starts, {}, dus, {}));
}
+TEST_F(CanShareOperandBufferWithUserTest, SortCanShare) {
+ auto builder = HloComputation::Builder(TestName());
+
+ Shape keys_shape = ShapeUtil::MakeShape(F32, {8});
+ auto keys = builder.AddInstruction(
+ HloInstruction::CreateParameter(0, keys_shape, "keys"));
+ auto sort =
+ builder.AddInstruction(HloInstruction::CreateSort(keys_shape, 0, keys));
+
+ BuildModuleAndRunAnalysis(builder.Build());
+
+ EXPECT_TRUE(
+ points_to_analysis_->CanShareOperandBufferWithUser(keys, {}, sort, {}));
+}
+
+TEST_F(CanShareOperandBufferWithUserTest, SortCanShareWithTupleUser) {
+ auto builder = HloComputation::Builder(TestName());
+
+ Shape keys_shape = ShapeUtil::MakeShape(F32, {8});
+ Shape values_shape = ShapeUtil::MakeShape(F32, {8});
+ auto keys = builder.AddInstruction(
+ HloInstruction::CreateParameter(0, keys_shape, "keys"));
+ auto values = builder.AddInstruction(
+ HloInstruction::CreateParameter(1, values_shape, "values"));
+ auto sort = builder.AddInstruction(HloInstruction::CreateSort(
+ ShapeUtil::MakeTupleShape({keys_shape, values_shape}), 0, keys, values));
+
+ BuildModuleAndRunAnalysis(builder.Build());
+
+ // The buffer for the keys can be shared with the first tuple entry.
+ EXPECT_TRUE(
+ points_to_analysis_->CanShareOperandBufferWithUser(keys, {}, sort, {0}));
+ // The buffer for the values can be shared with the second tuple entry.
+ EXPECT_TRUE(points_to_analysis_->CanShareOperandBufferWithUser(values, {},
+ sort, {1}));
+ // Verify that the buffers are not shared with the "wrong" tuple entry.
+ EXPECT_FALSE(
+ points_to_analysis_->CanShareOperandBufferWithUser(keys, {}, sort, {1}));
+ EXPECT_FALSE(points_to_analysis_->CanShareOperandBufferWithUser(values, {},
+ sort, {0}));
+}
+
TEST_F(CanShareOperandBufferWithUserTest, FusedDotAdd) {
auto builder = HloComputation::Builder(TestName());
Shape data_shape = ShapeUtil::MakeShape(F32, {2, 2});
diff --git a/tensorflow/compiler/xla/tests/BUILD b/tensorflow/compiler/xla/tests/BUILD
index e840067056..42d52aee78 100644
--- a/tensorflow/compiler/xla/tests/BUILD
+++ b/tensorflow/compiler/xla/tests/BUILD
@@ -154,8 +154,8 @@ tf_cc_binary(
"//tensorflow/compiler/xla:types",
"//tensorflow/compiler/xla:util",
"//tensorflow/compiler/xla/client:client_library",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/service/cpu:cpu_compiler",
"//tensorflow/compiler/xla/service/llvm_ir:llvm_util",
"//tensorflow/core:lib",
@@ -192,8 +192,8 @@ cc_library(
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/service:interpreter_plugin", # reference backend
"//tensorflow/compiler/xla/service:platform_util",
"//tensorflow/compiler/xla/tests:literal_test_util",
@@ -262,7 +262,7 @@ cc_library(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/service:computation_placer",
"//tensorflow/compiler/xla/service:device_memory_allocator",
"//tensorflow/compiler/xla/service:local_service",
@@ -290,8 +290,8 @@ xla_test(
"//tensorflow/compiler/xla:types",
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
"//tensorflow/core:lib",
@@ -314,8 +314,8 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
"//tensorflow/core:test",
@@ -334,8 +334,8 @@ xla_test(
"//tensorflow/compiler/xla:test_helpers",
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
"//tensorflow/core:test",
@@ -356,9 +356,9 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:arithmetic",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/compiler/xla/service:platform_util",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
@@ -376,9 +376,10 @@ xla_test(
"//tensorflow/compiler/xla:shape_util",
"//tensorflow/compiler/xla:util",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/service:platform_util",
+ "//tensorflow/compiler/xla/service:stream_pool",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:test_utils",
"//tensorflow/core:lib",
@@ -395,8 +396,8 @@ xla_test(
],
deps = [
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -419,9 +420,9 @@ xla_test(
"//tensorflow/compiler/xla:xla_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:arithmetic",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:test_utils",
@@ -445,8 +446,8 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -464,9 +465,9 @@ xla_test(
deps = [
"//tensorflow/compiler/xla:array2d",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:arithmetic",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
"//tensorflow/core:test",
@@ -483,8 +484,8 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -501,8 +502,8 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -519,9 +520,9 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:arithmetic",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -543,8 +544,8 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -562,8 +563,8 @@ xla_test(
"//tensorflow/compiler/xla:test_helpers",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
"//tensorflow/core:lib",
@@ -586,8 +587,8 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
"//tensorflow/core:lib",
@@ -612,8 +613,8 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:hlo_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
@@ -638,7 +639,7 @@ xla_test(
deps = [
":client_library_test_base",
":literal_test_util",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
"//tensorflow/core:lib",
],
@@ -658,7 +659,7 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/service:reduce_precision_insertion",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
@@ -681,8 +682,8 @@ xla_test(
"//tensorflow/compiler/xla:reference_util",
"//tensorflow/compiler/xla:shape_util",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:test_utils",
@@ -702,7 +703,7 @@ xla_test(
"//tensorflow/compiler/xla:execution_options_util",
"//tensorflow/compiler/xla:status_macros",
"//tensorflow/compiler/xla:test",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/service:hlo_parser",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
],
@@ -725,8 +726,8 @@ xla_test(
"//tensorflow/compiler/xla:reference_util",
"//tensorflow/compiler/xla:shape_util",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:test_utils",
@@ -749,8 +750,8 @@ xla_test(
"//tensorflow/compiler/xla:shape_util",
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:hlo_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
@@ -773,8 +774,8 @@ xla_test(
"//tensorflow/compiler/xla:literal_util",
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -795,7 +796,7 @@ CONVOLUTION_TEST_DEPS = [
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
"//tensorflow/compiler/xla/client:padding",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -838,8 +839,8 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:local_client",
"//tensorflow/compiler/xla/client:padding",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -862,8 +863,8 @@ xla_test(
"//tensorflow/compiler/xla:util",
"//tensorflow/compiler/xla/client:local_client",
"//tensorflow/compiler/xla/client:padding",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -891,10 +892,10 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:arithmetic",
"//tensorflow/compiler/xla/client/lib:math",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/compiler/xla/service:hlo",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:hlo_test_base",
@@ -924,9 +925,9 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:arithmetic",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/compiler/xla/service:hlo",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:hlo_test_base",
@@ -950,8 +951,8 @@ xla_test(
"//tensorflow/compiler/xla:statusor",
"//tensorflow/compiler/xla:test",
"//tensorflow/compiler/xla:test_helpers",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:hlo_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
@@ -972,7 +973,7 @@ xla_test(
"//tensorflow/compiler/xla:array2d",
"//tensorflow/compiler/xla:reference_util",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -991,8 +992,8 @@ xla_test(
"//tensorflow/compiler/xla:array2d",
"//tensorflow/compiler/xla:array3d",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1013,7 +1014,7 @@ xla_test(
"//tensorflow/compiler/xla:test_helpers",
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/service:computation_placer",
"//tensorflow/compiler/xla/service:device_memory_allocator",
"//tensorflow/compiler/xla/service:local_service",
@@ -1044,8 +1045,8 @@ xla_test(
"//tensorflow/compiler/xla:test_helpers",
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:hlo_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
@@ -1065,9 +1066,9 @@ xla_test(
"//tensorflow/compiler/xla:array3d",
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:arithmetic",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1096,9 +1097,9 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:arithmetic",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1123,9 +1124,9 @@ xla_test_library(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:local_client",
"//tensorflow/compiler/xla/client:padding",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:arithmetic",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:hlo_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
@@ -1164,9 +1165,9 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:local_client",
"//tensorflow/compiler/xla/client:padding",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:arithmetic",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1187,7 +1188,7 @@ xla_test(
"//tensorflow/compiler/xla:literal",
"//tensorflow/compiler/xla:util",
"//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/service:hlo",
"//tensorflow/compiler/xla/tests:hlo_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
@@ -1241,8 +1242,8 @@ xla_test(
"//tensorflow/compiler/xla:shape_util",
"//tensorflow/compiler/xla:test_helpers",
"//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1260,7 +1261,7 @@ xla_test(
"//tensorflow/compiler/xla:shape_util",
"//tensorflow/compiler/xla:util",
"//tensorflow/compiler/xla:xla_data_proto",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/service:hlo",
"//tensorflow/compiler/xla/service/cpu:custom_call_target_registry",
"//tensorflow/compiler/xla/tests:client_library_test_base",
@@ -1283,8 +1284,8 @@ xla_test(
"//tensorflow/compiler/xla:array4d",
"//tensorflow/compiler/xla:reference_util",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1306,8 +1307,8 @@ xla_test(
"//tensorflow/compiler/xla:statusor",
"//tensorflow/compiler/xla:test",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1327,8 +1328,8 @@ xla_test(
"//tensorflow/compiler/xla:util",
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/client/lib:arithmetic",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1345,8 +1346,8 @@ xla_test(
],
deps = [
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1362,8 +1363,8 @@ xla_test(
],
deps = [
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1387,8 +1388,8 @@ xla_test(
"//tensorflow/compiler/xla:util",
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:test_utils",
@@ -1408,8 +1409,8 @@ xla_test(
"//tensorflow/compiler/xla:util",
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
"//tensorflow/core:lib",
@@ -1438,8 +1439,8 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1458,7 +1459,7 @@ xla_test(
"//tensorflow/compiler/xla:array2d",
"//tensorflow/compiler/xla:array4d",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1481,9 +1482,9 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:arithmetic",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1507,8 +1508,8 @@ xla_test(
"//tensorflow/compiler/xla:test",
"//tensorflow/compiler/xla:test_helpers",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1527,8 +1528,8 @@ xla_test(
"//tensorflow/compiler/xla:types",
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1549,8 +1550,8 @@ xla_test(
"//tensorflow/compiler/xla:types",
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/service:hlo_parser",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:hlo_test_base",
@@ -1572,7 +1573,7 @@ xla_test(
"//tensorflow/compiler/xla:shape_util",
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1593,8 +1594,8 @@ xla_test(
"//tensorflow/compiler/xla:xla_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:test_utils",
@@ -1612,8 +1613,8 @@ xla_test(
],
deps = [
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1635,8 +1636,8 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:global_data",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:test_utils",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1656,8 +1657,8 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:test_utils",
@@ -1673,8 +1674,8 @@ xla_test(
deps = [
":client_library_test_base",
"//tensorflow/compiler/xla/client:global_data",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
"//tensorflow/core:test",
],
@@ -1687,8 +1688,8 @@ xla_test(
deps = [
":client_library_test_base",
"//tensorflow/compiler/xla/client:global_data",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
"//tensorflow/core:test",
],
@@ -1708,8 +1709,8 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/service:hlo_proto",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
@@ -1793,8 +1794,8 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/service:hlo",
"//tensorflow/compiler/xla/service:hlo_parser",
"//tensorflow/compiler/xla/service:hlo_runner",
@@ -1821,8 +1822,8 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/service:hlo",
"//tensorflow/compiler/xla/service:hlo_runner",
"//tensorflow/compiler/xla/service:platform_util",
@@ -1858,8 +1859,8 @@ xla_test(
"//tensorflow/compiler/xla:literal",
"//tensorflow/compiler/xla:statusor",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/service:local_service",
"//tensorflow/compiler/xla/service:shaped_buffer",
"//tensorflow/compiler/xla/tests:literal_test_util",
@@ -1886,8 +1887,8 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/service:device_memory_allocator",
"//tensorflow/compiler/xla/service:local_service",
"//tensorflow/compiler/xla/service:platform_util",
@@ -1903,6 +1904,16 @@ xla_test(
],
)
+xla_test(
+ name = "outfeed_in_nested_computation_test",
+ srcs = ["outfeed_in_nested_computation_test.cc"],
+ deps = [
+ "//tensorflow/compiler/xla/tests:local_client_test_base",
+ "//tensorflow/compiler/xla/tests:xla_internal_test_main",
+ "//tensorflow/core:test",
+ ],
+)
+
tf_cc_test(
name = "hlo_metadata_test",
srcs = [
@@ -1912,7 +1923,7 @@ tf_cc_test(
":local_client_test_base",
"//tensorflow/compiler/xla:test_helpers",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/service:cpu_plugin",
"//tensorflow/compiler/xla/service:local_service",
"//tensorflow/core:test_main",
@@ -1954,8 +1965,8 @@ xla_test(
"//tensorflow/compiler/xla:xla_data_proto",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:literal_test_util",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
@@ -1968,7 +1979,7 @@ xla_test(
name = "deep_graph_test",
srcs = ["deep_graph_test.cc"],
deps = [
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/compiler/xla/tests:client_library_test_base",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
],
@@ -2001,6 +2012,7 @@ xla_test(
"//tensorflow/compiler/xla/service:device_memory_allocator",
"//tensorflow/compiler/xla/service:generic_transfer_manager",
"//tensorflow/compiler/xla/service:shaped_buffer",
+ "//tensorflow/compiler/xla/service:stream_pool",
"//tensorflow/core:lib",
"//tensorflow/core:stream_executor_no_cuda",
"//tensorflow/core:test",
@@ -2053,8 +2065,8 @@ xla_test(
":local_client_test_base",
":test_utils",
"//tensorflow/compiler/xla:shape_util",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
- "//tensorflow/compiler/xla/client/xla_client:xla_computation",
+ "//tensorflow/compiler/xla/client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/service:hlo_parser",
"//tensorflow/compiler/xla/tests:xla_internal_test_main",
"//tensorflow/core:test",
@@ -2075,7 +2087,7 @@ xla_test(
":client_library_test_base",
":literal_test_util",
":xla_internal_test_main",
- "//tensorflow/compiler/xla/client/xla_client:xla_builder",
+ "//tensorflow/compiler/xla/client:xla_builder",
"//tensorflow/core:lib",
"//tensorflow/core:test",
],
diff --git a/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc b/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc
index 3ae96fa1bc..74f2e36f82 100644
--- a/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc
+++ b/tensorflow/compiler/xla/tests/array_elementwise_ops_test.cc
@@ -24,7 +24,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array4d.h"
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/layout_util.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/statusor.h"
diff --git a/tensorflow/compiler/xla/tests/axpy_simple_test.cc b/tensorflow/compiler/xla/tests/axpy_simple_test.cc
index 8d15b7841b..caeb0bf49a 100644
--- a/tensorflow/compiler/xla/tests/axpy_simple_test.cc
+++ b/tensorflow/compiler/xla/tests/axpy_simple_test.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include <vector>
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
#include "tensorflow/compiler/xla/tests/test_macros.h"
diff --git a/tensorflow/compiler/xla/tests/bad_rng_shape_validation_test.cc b/tensorflow/compiler/xla/tests/bad_rng_shape_validation_test.cc
index 8c227df7f0..af0b852239 100644
--- a/tensorflow/compiler/xla/tests/bad_rng_shape_validation_test.cc
+++ b/tensorflow/compiler/xla/tests/bad_rng_shape_validation_test.cc
@@ -19,8 +19,8 @@ limitations under the License.
#include <memory>
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/compiler/xla/test.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
diff --git a/tensorflow/compiler/xla/tests/batch_normalization_test.cc b/tensorflow/compiler/xla/tests/batch_normalization_test.cc
index 6a024798f9..d372d1ca43 100644
--- a/tensorflow/compiler/xla/tests/batch_normalization_test.cc
+++ b/tensorflow/compiler/xla/tests/batch_normalization_test.cc
@@ -22,8 +22,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/lib/math.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/reference_util.h"
#include "tensorflow/compiler/xla/service/hlo_computation.h"
diff --git a/tensorflow/compiler/xla/tests/bfloat16_test.cc b/tensorflow/compiler/xla/tests/bfloat16_test.cc
index 747c82b502..6c20f654fe 100644
--- a/tensorflow/compiler/xla/tests/bfloat16_test.cc
+++ b/tensorflow/compiler/xla/tests/bfloat16_test.cc
@@ -21,7 +21,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array4d.h"
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/reference_util.h"
#include "tensorflow/compiler/xla/service/hlo_computation.h"
diff --git a/tensorflow/compiler/xla/tests/binop_scaling_test.cc b/tensorflow/compiler/xla/tests/binop_scaling_test.cc
index 20cb989751..0d7a3aa46a 100644
--- a/tensorflow/compiler/xla/tests/binop_scaling_test.cc
+++ b/tensorflow/compiler/xla/tests/binop_scaling_test.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array2d.h"
#include "tensorflow/compiler/xla/array4d.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/reference_util.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
diff --git a/tensorflow/compiler/xla/tests/bitcast_convert_test.cc b/tensorflow/compiler/xla/tests/bitcast_convert_test.cc
index d531e8fa82..c6b5108fe9 100644
--- a/tensorflow/compiler/xla/tests/bitcast_convert_test.cc
+++ b/tensorflow/compiler/xla/tests/bitcast_convert_test.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include <vector>
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
diff --git a/tensorflow/compiler/xla/tests/broadcast_simple_test.cc b/tensorflow/compiler/xla/tests/broadcast_simple_test.cc
index 50dd574624..1d28e85b16 100644
--- a/tensorflow/compiler/xla/tests/broadcast_simple_test.cc
+++ b/tensorflow/compiler/xla/tests/broadcast_simple_test.cc
@@ -20,7 +20,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array2d.h"
#include "tensorflow/compiler/xla/array4d.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/compiler/xla/statusor.h"
diff --git a/tensorflow/compiler/xla/tests/call_test.cc b/tensorflow/compiler/xla/tests/call_test.cc
index 2086e38b91..b1d18210ea 100644
--- a/tensorflow/compiler/xla/tests/call_test.cc
+++ b/tensorflow/compiler/xla/tests/call_test.cc
@@ -16,8 +16,8 @@ limitations under the License.
#include <memory>
#include <utility>
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/compiler/xla/shape_util.h"
diff --git a/tensorflow/compiler/xla/tests/check_execution_arity_test.cc b/tensorflow/compiler/xla/tests/check_execution_arity_test.cc
index 0bc8facfe2..a4eb57fc7b 100644
--- a/tensorflow/compiler/xla/tests/check_execution_arity_test.cc
+++ b/tensorflow/compiler/xla/tests/check_execution_arity_test.cc
@@ -17,7 +17,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/statusor.h"
diff --git a/tensorflow/compiler/xla/tests/client_library_test_base.cc b/tensorflow/compiler/xla/tests/client_library_test_base.cc
index 7a2e70d39f..59d917054b 100644
--- a/tensorflow/compiler/xla/tests/client_library_test_base.cc
+++ b/tensorflow/compiler/xla/tests/client_library_test_base.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/client_library.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/execution_options_util.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/compiler/xla/ptr_util.h"
diff --git a/tensorflow/compiler/xla/tests/client_library_test_base.h b/tensorflow/compiler/xla/tests/client_library_test_base.h
index f0f7ff1ea0..4a6e8a3124 100644
--- a/tensorflow/compiler/xla/tests/client_library_test_base.h
+++ b/tensorflow/compiler/xla/tests/client_library_test_base.h
@@ -26,8 +26,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/array4d.h"
#include "tensorflow/compiler/xla/client/client_library.h"
#include "tensorflow/compiler/xla/client/global_data.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/compiler/xla/ptr_util.h"
diff --git a/tensorflow/compiler/xla/tests/client_test.cc b/tensorflow/compiler/xla/tests/client_test.cc
index 6ce2f844a3..c898dacf48 100644
--- a/tensorflow/compiler/xla/tests/client_test.cc
+++ b/tensorflow/compiler/xla/tests/client_test.cc
@@ -18,8 +18,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/status_macros.h"
#include "tensorflow/compiler/xla/statusor.h"
diff --git a/tensorflow/compiler/xla/tests/compilation_cache_test.cc b/tensorflow/compiler/xla/tests/compilation_cache_test.cc
index ff38246286..7c52c9fbbb 100644
--- a/tensorflow/compiler/xla/tests/compilation_cache_test.cc
+++ b/tensorflow/compiler/xla/tests/compilation_cache_test.cc
@@ -19,8 +19,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/statusor.h"
diff --git a/tensorflow/compiler/xla/tests/compute_constant_test.cc b/tensorflow/compiler/xla/tests/compute_constant_test.cc
index 64bf8b3b38..5a06d061f0 100644
--- a/tensorflow/compiler/xla/tests/compute_constant_test.cc
+++ b/tensorflow/compiler/xla/tests/compute_constant_test.cc
@@ -19,8 +19,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/client_library.h"
#include "tensorflow/compiler/xla/client/global_data.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/layout_util.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/shape_util.h"
diff --git a/tensorflow/compiler/xla/tests/concat_test.cc b/tensorflow/compiler/xla/tests/concat_test.cc
index 9f288634c0..be017477d8 100644
--- a/tensorflow/compiler/xla/tests/concat_test.cc
+++ b/tensorflow/compiler/xla/tests/concat_test.cc
@@ -19,8 +19,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/array2d.h"
#include "tensorflow/compiler/xla/array3d.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/reference_util.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/compiler/xla/test.h"
diff --git a/tensorflow/compiler/xla/tests/conditional_test.cc b/tensorflow/compiler/xla/tests/conditional_test.cc
index 369663de15..b27c1044ba 100644
--- a/tensorflow/compiler/xla/tests/conditional_test.cc
+++ b/tensorflow/compiler/xla/tests/conditional_test.cc
@@ -13,8 +13,8 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
#include "tensorflow/compiler/xla/tests/test_macros.h"
diff --git a/tensorflow/compiler/xla/tests/constants_test.cc b/tensorflow/compiler/xla/tests/constants_test.cc
index 71d72a9828..4937574831 100644
--- a/tensorflow/compiler/xla/tests/constants_test.cc
+++ b/tensorflow/compiler/xla/tests/constants_test.cc
@@ -22,7 +22,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array3d.h"
#include "tensorflow/compiler/xla/array4d.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
diff --git a/tensorflow/compiler/xla/tests/convert_test.cc b/tensorflow/compiler/xla/tests/convert_test.cc
index 0fb6853e3f..1adc68cc48 100644
--- a/tensorflow/compiler/xla/tests/convert_test.cc
+++ b/tensorflow/compiler/xla/tests/convert_test.cc
@@ -20,7 +20,7 @@ limitations under the License.
#include <vector>
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
diff --git a/tensorflow/compiler/xla/tests/convolution_dimension_numbers_test.cc b/tensorflow/compiler/xla/tests/convolution_dimension_numbers_test.cc
index 944366410b..7b6bbc4f57 100644
--- a/tensorflow/compiler/xla/tests/convolution_dimension_numbers_test.cc
+++ b/tensorflow/compiler/xla/tests/convolution_dimension_numbers_test.cc
@@ -20,7 +20,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array4d.h"
#include "tensorflow/compiler/xla/client/local_client.h"
#include "tensorflow/compiler/xla/client/padding.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/ptr_util.h"
#include "tensorflow/compiler/xla/reference_util.h"
#include "tensorflow/compiler/xla/statusor.h"
diff --git a/tensorflow/compiler/xla/tests/convolution_test.cc b/tensorflow/compiler/xla/tests/convolution_test.cc
index a8b8f74ca9..5ed8122e00 100644
--- a/tensorflow/compiler/xla/tests/convolution_test.cc
+++ b/tensorflow/compiler/xla/tests/convolution_test.cc
@@ -23,7 +23,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/local_client.h"
#include "tensorflow/compiler/xla/client/padding.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/layout_util.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/ptr_util.h"
diff --git a/tensorflow/compiler/xla/tests/convolution_variants_test.cc b/tensorflow/compiler/xla/tests/convolution_variants_test.cc
index 8792e7781b..6784c16715 100644
--- a/tensorflow/compiler/xla/tests/convolution_variants_test.cc
+++ b/tensorflow/compiler/xla/tests/convolution_variants_test.cc
@@ -27,7 +27,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array4d.h"
#include "tensorflow/compiler/xla/client/local_client.h"
#include "tensorflow/compiler/xla/client/padding.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/reference_util.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
diff --git a/tensorflow/compiler/xla/tests/copy_test.cc b/tensorflow/compiler/xla/tests/copy_test.cc
index 1dc6ff0f4f..5ef273e5a2 100644
--- a/tensorflow/compiler/xla/tests/copy_test.cc
+++ b/tensorflow/compiler/xla/tests/copy_test.cc
@@ -17,7 +17,7 @@ limitations under the License.
#include <utility>
#include "tensorflow/compiler/xla/array2d.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/ptr_util.h"
#include "tensorflow/compiler/xla/service/hlo_computation.h"
diff --git a/tensorflow/compiler/xla/tests/custom_call_test.cc b/tensorflow/compiler/xla/tests/custom_call_test.cc
index 90f3d1b874..13c777835e 100644
--- a/tensorflow/compiler/xla/tests/custom_call_test.cc
+++ b/tensorflow/compiler/xla/tests/custom_call_test.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include <memory>
#include <utility>
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/compiler/xla/ptr_util.h"
#include "tensorflow/compiler/xla/service/cpu/custom_call_target_registry.h"
diff --git a/tensorflow/compiler/xla/tests/deallocation_test.cc b/tensorflow/compiler/xla/tests/deallocation_test.cc
index d4b3aac85b..5f234f36a8 100644
--- a/tensorflow/compiler/xla/tests/deallocation_test.cc
+++ b/tensorflow/compiler/xla/tests/deallocation_test.cc
@@ -17,8 +17,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/compiler/xla/test.h"
#include "tensorflow/compiler/xla/test_helpers.h"
diff --git a/tensorflow/compiler/xla/tests/deconstruct_tuple_test.cc b/tensorflow/compiler/xla/tests/deconstruct_tuple_test.cc
index a6a233e71a..2db6503afa 100644
--- a/tensorflow/compiler/xla/tests/deconstruct_tuple_test.cc
+++ b/tensorflow/compiler/xla/tests/deconstruct_tuple_test.cc
@@ -18,8 +18,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/statusor.h"
diff --git a/tensorflow/compiler/xla/tests/deep_graph_test.cc b/tensorflow/compiler/xla/tests/deep_graph_test.cc
index 810947ab01..3f3e8ab712 100644
--- a/tensorflow/compiler/xla/tests/deep_graph_test.cc
+++ b/tensorflow/compiler/xla/tests/deep_graph_test.cc
@@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
namespace xla {
diff --git a/tensorflow/compiler/xla/tests/dot_operation_test.cc b/tensorflow/compiler/xla/tests/dot_operation_test.cc
index d86fd7cc2d..cfd36abf47 100644
--- a/tensorflow/compiler/xla/tests/dot_operation_test.cc
+++ b/tensorflow/compiler/xla/tests/dot_operation_test.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array2d.h"
#include "tensorflow/compiler/xla/array3d.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/primitive_util.h"
#include "tensorflow/compiler/xla/reference_util.h"
#include "tensorflow/compiler/xla/shape_util.h"
diff --git a/tensorflow/compiler/xla/tests/dynamic_ops_test.cc b/tensorflow/compiler/xla/tests/dynamic_ops_test.cc
index 88ac96d6b0..7f6f203a1b 100644
--- a/tensorflow/compiler/xla/tests/dynamic_ops_test.cc
+++ b/tensorflow/compiler/xla/tests/dynamic_ops_test.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array2d.h"
#include "tensorflow/compiler/xla/client/client_library.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/reference_util.h"
#include "tensorflow/compiler/xla/service/device_memory_allocator.h"
#include "tensorflow/compiler/xla/service/local_service.h"
diff --git a/tensorflow/compiler/xla/tests/execution_profile_test.cc b/tensorflow/compiler/xla/tests/execution_profile_test.cc
index ebba13c5b3..5116e60ca6 100644
--- a/tensorflow/compiler/xla/tests/execution_profile_test.cc
+++ b/tensorflow/compiler/xla/tests/execution_profile_test.cc
@@ -14,8 +14,8 @@ limitations under the License.
==============================================================================*/
#include "tensorflow/compiler/xla/client/global_data.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/test_macros.h"
#include "tensorflow/core/platform/test.h"
diff --git a/tensorflow/compiler/xla/tests/exhaustive_f32_elementwise_op_test.cc b/tensorflow/compiler/xla/tests/exhaustive_f32_elementwise_op_test.cc
index 86bfaea4ef..bf1de02ba9 100644
--- a/tensorflow/compiler/xla/tests/exhaustive_f32_elementwise_op_test.cc
+++ b/tensorflow/compiler/xla/tests/exhaustive_f32_elementwise_op_test.cc
@@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
#include "tensorflow/compiler/xla/tests/test_macros.h"
diff --git a/tensorflow/compiler/xla/tests/floor_ceil_test.cc b/tensorflow/compiler/xla/tests/floor_ceil_test.cc
index 30dc639f11..39cc6c5927 100644
--- a/tensorflow/compiler/xla/tests/floor_ceil_test.cc
+++ b/tensorflow/compiler/xla/tests/floor_ceil_test.cc
@@ -17,7 +17,7 @@ limitations under the License.
#include <string>
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
#include "tensorflow/compiler/xla/tests/test_macros.h"
diff --git a/tensorflow/compiler/xla/tests/fmax_test.cc b/tensorflow/compiler/xla/tests/fmax_test.cc
index 0254ae1baa..c5bbbe778d 100644
--- a/tensorflow/compiler/xla/tests/fmax_test.cc
+++ b/tensorflow/compiler/xla/tests/fmax_test.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include <vector>
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
#include "tensorflow/core/platform/test.h"
diff --git a/tensorflow/compiler/xla/tests/fusion_test.cc b/tensorflow/compiler/xla/tests/fusion_test.cc
index 607bcdd51e..792be0d3fc 100644
--- a/tensorflow/compiler/xla/tests/fusion_test.cc
+++ b/tensorflow/compiler/xla/tests/fusion_test.cc
@@ -25,7 +25,7 @@ limitations under the License.
#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor"
#include "tensorflow/compiler/xla/array2d.h"
#include "tensorflow/compiler/xla/client/client_library.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/primitive_util.h"
#include "tensorflow/compiler/xla/ptr_util.h"
diff --git a/tensorflow/compiler/xla/tests/gather_operation_test.cc b/tensorflow/compiler/xla/tests/gather_operation_test.cc
index c5ca64fa3f..b77bece85a 100644
--- a/tensorflow/compiler/xla/tests/gather_operation_test.cc
+++ b/tensorflow/compiler/xla/tests/gather_operation_test.cc
@@ -13,7 +13,7 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/execution_options_util.h"
#include "tensorflow/compiler/xla/service/hlo_parser.h"
#include "tensorflow/compiler/xla/status_macros.h"
diff --git a/tensorflow/compiler/xla/tests/half_test.cc b/tensorflow/compiler/xla/tests/half_test.cc
index 73a47eda72..51450314b6 100644
--- a/tensorflow/compiler/xla/tests/half_test.cc
+++ b/tensorflow/compiler/xla/tests/half_test.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include <cmath>
#include <vector>
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/compiler/xla/test.h"
@@ -48,7 +48,8 @@ class UnaryOpTest : public HalfTestBase,
public ::testing::WithParamInterface<UnaryOpTestParam> {};
XLA_TEST_P(UnaryOpTest, Ops) {
- std::vector<half> x({half(1.4), half(-2.3), half(3.2), half(-4.1)});
+ std::vector<half> x({half(1.4), half(-2.3), half(3.2), half(-4.1), half(9.0),
+ half(42.0), half(-9.0), half(-100.0)});
XlaBuilder builder(TestName());
XlaOp x_opnd;
auto x_data = CreateR1Parameter<half>(x, /*parameter_number=*/0, "x",
diff --git a/tensorflow/compiler/xla/tests/hlo_metadata_test.cc b/tensorflow/compiler/xla/tests/hlo_metadata_test.cc
index 4d82442f7e..5511190caf 100644
--- a/tensorflow/compiler/xla/tests/hlo_metadata_test.cc
+++ b/tensorflow/compiler/xla/tests/hlo_metadata_test.cc
@@ -14,7 +14,7 @@ limitations under the License.
==============================================================================*/
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/service/local_service.h"
#include "tensorflow/compiler/xla/test_helpers.h"
#include "tensorflow/compiler/xla/tests/local_client_test_base.h"
diff --git a/tensorflow/compiler/xla/tests/local_client_allocation_test.cc b/tensorflow/compiler/xla/tests/local_client_allocation_test.cc
index 0df50150ae..e2cd5bcc5a 100644
--- a/tensorflow/compiler/xla/tests/local_client_allocation_test.cc
+++ b/tensorflow/compiler/xla/tests/local_client_allocation_test.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include <memory>
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/service/local_service.h"
#include "tensorflow/compiler/xla/service/shaped_buffer.h"
diff --git a/tensorflow/compiler/xla/tests/local_client_aot_test_helper.cc b/tensorflow/compiler/xla/tests/local_client_aot_test_helper.cc
index 70612e7c49..74494e60e8 100644
--- a/tensorflow/compiler/xla/tests/local_client_aot_test_helper.cc
+++ b/tensorflow/compiler/xla/tests/local_client_aot_test_helper.cc
@@ -21,8 +21,8 @@ limitations under the License.
#include "llvm/ADT/Triple.h"
#include "tensorflow/compiler/xla/client/client_library.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/service/cpu/cpu_compiler.h"
#include "tensorflow/compiler/xla/service/llvm_ir/llvm_util.h"
#include "tensorflow/compiler/xla/types.h"
@@ -92,9 +92,10 @@ int main(int argc, char** argv) {
// It's lame to hard-code the buffer assignments, but we need
// local_client_aot_test.cc to be able to easily invoke the function.
CHECK_EQ(result->result_buffer_index(), 1);
- CHECK_EQ(result->buffer_sizes().size(), 2);
+ CHECK_EQ(result->buffer_sizes().size(), 3);
CHECK_EQ(result->buffer_sizes()[0], -1); // param buffer
CHECK_EQ(result->buffer_sizes()[1], sizeof(float)); // result buffer
+ CHECK_EQ(result->buffer_sizes()[2], -1); // const buffer
if (triple.isOSBinFormatELF()) {
// Check the ELF magic.
CHECK_EQ(result->object_file_data()[0], 0x7F);
diff --git a/tensorflow/compiler/xla/tests/local_client_execute_test.cc b/tensorflow/compiler/xla/tests/local_client_execute_test.cc
index 5c3498c84c..1a823cf189 100644
--- a/tensorflow/compiler/xla/tests/local_client_execute_test.cc
+++ b/tensorflow/compiler/xla/tests/local_client_execute_test.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/client_library.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/layout_util.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/service/device_memory_allocator.h"
diff --git a/tensorflow/compiler/xla/tests/local_client_test_base.cc b/tensorflow/compiler/xla/tests/local_client_test_base.cc
index c31ba0e713..eaddf756db 100644
--- a/tensorflow/compiler/xla/tests/local_client_test_base.cc
+++ b/tensorflow/compiler/xla/tests/local_client_test_base.cc
@@ -20,6 +20,7 @@ limitations under the License.
#include "third_party/eigen3/unsupported/Eigen/CXX11/Tensor"
#include "tensorflow/compiler/xla/client/local_client.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/map_util.h"
#include "tensorflow/compiler/xla/ptr_util.h"
#include "tensorflow/compiler/xla/shape_util.h"
diff --git a/tensorflow/compiler/xla/tests/local_client_test_base.h b/tensorflow/compiler/xla/tests/local_client_test_base.h
index 258226523d..b4477e9a6b 100644
--- a/tensorflow/compiler/xla/tests/local_client_test_base.h
+++ b/tensorflow/compiler/xla/tests/local_client_test_base.h
@@ -22,7 +22,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/client_library.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/service/device_memory_allocator.h"
#include "tensorflow/compiler/xla/service/local_service.h"
#include "tensorflow/compiler/xla/service/platform_util.h"
diff --git a/tensorflow/compiler/xla/tests/log_test.cc b/tensorflow/compiler/xla/tests/log_test.cc
index cdf70ee418..2d622242e6 100644
--- a/tensorflow/compiler/xla/tests/log_test.cc
+++ b/tensorflow/compiler/xla/tests/log_test.cc
@@ -17,7 +17,7 @@ limitations under the License.
#include <vector>
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
#include "tensorflow/compiler/xla/tests/test_macros.h"
diff --git a/tensorflow/compiler/xla/tests/map_test.cc b/tensorflow/compiler/xla/tests/map_test.cc
index 7ddc636931..0732e195d4 100644
--- a/tensorflow/compiler/xla/tests/map_test.cc
+++ b/tensorflow/compiler/xla/tests/map_test.cc
@@ -19,8 +19,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/statusor.h"
diff --git a/tensorflow/compiler/xla/tests/matrix_ops_simple_test.cc b/tensorflow/compiler/xla/tests/matrix_ops_simple_test.cc
index 069b8a881f..da8c42d465 100644
--- a/tensorflow/compiler/xla/tests/matrix_ops_simple_test.cc
+++ b/tensorflow/compiler/xla/tests/matrix_ops_simple_test.cc
@@ -19,8 +19,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/array2d.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/ptr_util.h"
#include "tensorflow/compiler/xla/reference_util.h"
diff --git a/tensorflow/compiler/xla/tests/multidimensional_slice_test.cc b/tensorflow/compiler/xla/tests/multidimensional_slice_test.cc
index e576f000ef..955dbef6dc 100644
--- a/tensorflow/compiler/xla/tests/multidimensional_slice_test.cc
+++ b/tensorflow/compiler/xla/tests/multidimensional_slice_test.cc
@@ -20,7 +20,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array2d.h"
#include "tensorflow/compiler/xla/array3d.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
#include "tensorflow/compiler/xla/tests/test_macros.h"
diff --git a/tensorflow/compiler/xla/tests/outfeed_in_nested_computation_test.cc b/tensorflow/compiler/xla/tests/outfeed_in_nested_computation_test.cc
new file mode 100644
index 0000000000..0a0426adcb
--- /dev/null
+++ b/tensorflow/compiler/xla/tests/outfeed_in_nested_computation_test.cc
@@ -0,0 +1,169 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/compiler/xla/tests/local_client_test_base.h"
+#include "tensorflow/compiler/xla/tests/test_macros.h"
+#include "tensorflow/core/lib/core/status_test_util.h"
+
+namespace xla {
+namespace {
+
+// Tests that ensure outfeed instructions that are contained in nested
+// computations in non-root positions are executed.
+
+class OutfeedInNestedComputationTest : public LocalClientTestBase {};
+
+XLA_TEST_F(OutfeedInNestedComputationTest, OutfeedInWhile) {
+ XlaBuilder b(TestName());
+
+ Shape state_tuple_array_shape = ShapeUtil::MakeShape(xla::S32, {10, 5});
+ Shape int_shape = ShapeUtil::MakeShape(xla::S32, {});
+ Shape state_tuple_shape =
+ ShapeUtil::MakeTupleShape({int_shape, state_tuple_array_shape});
+ Shape xfeed_shape = ShapeUtil::MakeShape(xla::S32, {2});
+
+ XlaOp some_buffer = Broadcast(ConstantR0<int32_t>(&b, 0), {10, 5});
+ XlaOp num_iter = Infeed(&b, int_shape);
+ XlaOp init_tuple = Tuple(&b, {num_iter, some_buffer});
+
+ TF_ASSERT_OK_AND_ASSIGN(XlaComputation loop_cond, [&] {
+ // Condition: iteration variable > 0
+ XlaBuilder cond_builder("loop_condition");
+ XlaOp state_tuple = Parameter(&cond_builder, 0, state_tuple_shape, "state");
+ XlaOp loop_counter = GetTupleElement(state_tuple, 0);
+ Outfeed(loop_counter, int_shape, "");
+ Gt(loop_counter, ConstantR0<int32_t>(&cond_builder, 0));
+ return cond_builder.Build();
+ }());
+
+ TF_ASSERT_OK_AND_ASSIGN(XlaComputation loop_body, [&] {
+ XlaBuilder body_builder("loop_body");
+ XlaOp state_tuple = Parameter(&body_builder, 0, state_tuple_shape, "state");
+ XlaOp loop_counter = GetTupleElement(state_tuple, 0);
+ XlaOp buffer_inside = GetTupleElement(state_tuple, 1);
+
+ // Read some stuff from Infeed.
+ XlaOp some_input = Infeed(&body_builder, xfeed_shape);
+ XlaOp sum = Add(some_input, Broadcast(loop_counter, {2}));
+ Outfeed(sum, xfeed_shape, "");
+
+ XlaOp iter_left = Sub(loop_counter, ConstantR0<int32_t>(&body_builder, 1));
+
+ Tuple(&body_builder, {iter_left, buffer_inside});
+ return body_builder.Build();
+ }());
+
+ // Build loop.
+ XlaOp result_tuple = While(loop_cond, loop_body, init_tuple);
+ GetTupleElement(result_tuple, 0);
+ TF_ASSERT_OK_AND_ASSIGN(XlaComputation computation, b.Build());
+
+ std::unique_ptr<xla::Literal> comp_result;
+ std::unique_ptr<tensorflow::Thread> thread(
+ tensorflow::Env::Default()->StartThread(
+ tensorflow::ThreadOptions(), "execute_thread", [&] {
+ comp_result = local_client_->ExecuteAndTransfer(computation, {})
+ .ConsumeValueOrDie();
+ }));
+
+ VLOG(1) << "Transferring trip count to computation";
+ // Transfer number of iterations to Infeed.
+ TF_ASSERT_OK(
+ local_client_->TransferToInfeed(*LiteralUtil::CreateR0<int32_t>(1)));
+
+ // Pick up value from outfeed
+ {
+ VLOG(1) << "Reading from condition outfeed";
+ TF_ASSERT_OK_AND_ASSIGN(std::unique_ptr<Literal> r,
+ local_client_->TransferFromOutfeed(&int_shape));
+ EXPECT_EQ(r->Get<int32>({}), 1);
+ }
+
+ VLOG(1) << "Writing data to infeed";
+ // Transfer some stuff to Infeed for use inside of loop.
+ TF_ASSERT_OK(local_client_->TransferToInfeed(
+ *LiteralUtil::CreateR1<int32_t>({10, 20})));
+
+ // Pick up value from outfeed
+ {
+ VLOG(1) << "Reading from body outfeed";
+ TF_ASSERT_OK_AND_ASSIGN(std::unique_ptr<Literal> r,
+ local_client_->TransferFromOutfeed(&xfeed_shape));
+ EXPECT_EQ(r->Get<int32>({0}), 11);
+ EXPECT_EQ(r->Get<int32>({1}), 21);
+ }
+
+ {
+ VLOG(1) << "Reading from condition outfeed";
+ TF_ASSERT_OK_AND_ASSIGN(std::unique_ptr<Literal> r,
+ local_client_->TransferFromOutfeed(&int_shape));
+ EXPECT_EQ(r->Get<int32>({}), 0);
+ }
+
+ // Joins the thread
+ thread.reset();
+
+ EXPECT_EQ(comp_result->Get<int32>({}), 0);
+}
+
+XLA_TEST_F(OutfeedInNestedComputationTest, OutfeedInConditional) {
+ XlaBuilder b(TestName());
+
+ Shape condition_shape = ShapeUtil::MakeShape(xla::PRED, {});
+ Shape result_shape = ShapeUtil::MakeShape(xla::PRED, {});
+
+ TF_ASSERT_OK_AND_ASSIGN(XlaComputation true_computation, [&] {
+ XlaBuilder inner_builder("true_computation");
+ XlaOp param = Parameter(&inner_builder, 0, result_shape, "param");
+ Outfeed(param, result_shape, "");
+ Or(param, param);
+ return inner_builder.Build();
+ }());
+
+ TF_ASSERT_OK_AND_ASSIGN(XlaComputation false_computation, [&] {
+ XlaBuilder inner_builder("false_computation");
+ Parameter(&inner_builder, 0, result_shape, "param");
+ return inner_builder.Build();
+ }());
+
+ XlaOp pred = Infeed(&b, condition_shape);
+ Conditional(/*predicate=*/pred, /*true_operand=*/pred,
+ /*true_computation=*/true_computation, /*false_operand=*/pred,
+ /*false_computation=*/false_computation);
+
+ TF_ASSERT_OK_AND_ASSIGN(XlaComputation computation, b.Build());
+
+ std::unique_ptr<xla::Literal> comp_result;
+ std::unique_ptr<tensorflow::Thread> thread(
+ tensorflow::Env::Default()->StartThread(
+ tensorflow::ThreadOptions(), "execute_thread", [&] {
+ comp_result = local_client_->ExecuteAndTransfer(computation, {})
+ .ConsumeValueOrDie();
+ }));
+
+ TF_ASSERT_OK(
+ local_client_->TransferToInfeed(*LiteralUtil::CreateR0<bool>(true)));
+
+ TF_ASSERT_OK_AND_ASSIGN(std::unique_ptr<Literal> r,
+ local_client_->TransferFromOutfeed(&result_shape));
+
+ EXPECT_EQ(r->Get<bool>({}), true);
+
+ // Join the thread
+ thread.reset();
+}
+
+} // namespace
+} // namespace xla
diff --git a/tensorflow/compiler/xla/tests/pad_test.cc b/tensorflow/compiler/xla/tests/pad_test.cc
index e428fa9b5e..ca21b0b2ba 100644
--- a/tensorflow/compiler/xla/tests/pad_test.cc
+++ b/tensorflow/compiler/xla/tests/pad_test.cc
@@ -20,7 +20,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array4d.h"
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/ptr_util.h"
#include "tensorflow/compiler/xla/reference_util.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
diff --git a/tensorflow/compiler/xla/tests/params_test.cc b/tensorflow/compiler/xla/tests/params_test.cc
index 8ba1d11b33..f6c762e7a4 100644
--- a/tensorflow/compiler/xla/tests/params_test.cc
+++ b/tensorflow/compiler/xla/tests/params_test.cc
@@ -21,8 +21,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/array2d.h"
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/layout_util.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/shape_util.h"
diff --git a/tensorflow/compiler/xla/tests/pred_test.cc b/tensorflow/compiler/xla/tests/pred_test.cc
index 5c351b2d11..2fc7f816b5 100644
--- a/tensorflow/compiler/xla/tests/pred_test.cc
+++ b/tensorflow/compiler/xla/tests/pred_test.cc
@@ -19,7 +19,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array2d.h"
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/core/lib/core/status_test_util.h"
#include "tensorflow/core/platform/test.h"
diff --git a/tensorflow/compiler/xla/tests/prng_test.cc b/tensorflow/compiler/xla/tests/prng_test.cc
index 3f98099be6..029af69573 100644
--- a/tensorflow/compiler/xla/tests/prng_test.cc
+++ b/tensorflow/compiler/xla/tests/prng_test.cc
@@ -17,7 +17,7 @@ limitations under the License.
#include <memory>
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/primitive_util.h"
#include "tensorflow/compiler/xla/shape_util.h"
diff --git a/tensorflow/compiler/xla/tests/query_inferred_shape_test.cc b/tensorflow/compiler/xla/tests/query_inferred_shape_test.cc
index 526a38e8d1..fab2a65de1 100644
--- a/tensorflow/compiler/xla/tests/query_inferred_shape_test.cc
+++ b/tensorflow/compiler/xla/tests/query_inferred_shape_test.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include <memory>
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/compiler/xla/test_helpers.h"
diff --git a/tensorflow/compiler/xla/tests/reduce_precision_test.cc b/tensorflow/compiler/xla/tests/reduce_precision_test.cc
index 04c7f31646..531648fe3e 100644
--- a/tensorflow/compiler/xla/tests/reduce_precision_test.cc
+++ b/tensorflow/compiler/xla/tests/reduce_precision_test.cc
@@ -22,7 +22,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array2d.h"
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/layout_util.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/service/reduce_precision_insertion.h"
diff --git a/tensorflow/compiler/xla/tests/reduce_test.cc b/tensorflow/compiler/xla/tests/reduce_test.cc
index e4a8ddf86a..2065271a7f 100644
--- a/tensorflow/compiler/xla/tests/reduce_test.cc
+++ b/tensorflow/compiler/xla/tests/reduce_test.cc
@@ -37,8 +37,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/layout_util.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/compiler/xla/reference_util.h"
diff --git a/tensorflow/compiler/xla/tests/reduce_window_test.cc b/tensorflow/compiler/xla/tests/reduce_window_test.cc
index c2681f70f7..1bd6fdab31 100644
--- a/tensorflow/compiler/xla/tests/reduce_window_test.cc
+++ b/tensorflow/compiler/xla/tests/reduce_window_test.cc
@@ -24,8 +24,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/local_client.h"
#include "tensorflow/compiler/xla/client/padding.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/reference_util.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
diff --git a/tensorflow/compiler/xla/tests/replay_test.cc b/tensorflow/compiler/xla/tests/replay_test.cc
index d544968648..d891451381 100644
--- a/tensorflow/compiler/xla/tests/replay_test.cc
+++ b/tensorflow/compiler/xla/tests/replay_test.cc
@@ -17,8 +17,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/protobuf_util.h"
#include "tensorflow/compiler/xla/service/hlo.pb.h"
diff --git a/tensorflow/compiler/xla/tests/reshape_motion_test.cc b/tensorflow/compiler/xla/tests/reshape_motion_test.cc
index 7c0389cfa3..368f5583c9 100644
--- a/tensorflow/compiler/xla/tests/reshape_motion_test.cc
+++ b/tensorflow/compiler/xla/tests/reshape_motion_test.cc
@@ -22,7 +22,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array4d.h"
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/layout_util.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/reference_util.h"
diff --git a/tensorflow/compiler/xla/tests/reshape_test.cc b/tensorflow/compiler/xla/tests/reshape_test.cc
index 46d91711a5..382d1b1ae7 100644
--- a/tensorflow/compiler/xla/tests/reshape_test.cc
+++ b/tensorflow/compiler/xla/tests/reshape_test.cc
@@ -22,8 +22,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/array4d.h"
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/layout_util.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/compiler/xla/reference_util.h"
diff --git a/tensorflow/compiler/xla/tests/reverse_test.cc b/tensorflow/compiler/xla/tests/reverse_test.cc
index 23f0d26d93..41e49b4003 100644
--- a/tensorflow/compiler/xla/tests/reverse_test.cc
+++ b/tensorflow/compiler/xla/tests/reverse_test.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array2d.h"
#include "tensorflow/compiler/xla/array4d.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
#include "tensorflow/compiler/xla/tests/test_macros.h"
diff --git a/tensorflow/compiler/xla/tests/scalar_computations_test.cc b/tensorflow/compiler/xla/tests/scalar_computations_test.cc
index 3b603c0d31..e42c71eb28 100644
--- a/tensorflow/compiler/xla/tests/scalar_computations_test.cc
+++ b/tensorflow/compiler/xla/tests/scalar_computations_test.cc
@@ -19,8 +19,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/compiler/xla/status_macros.h"
diff --git a/tensorflow/compiler/xla/tests/select_and_scatter_test.cc b/tensorflow/compiler/xla/tests/select_and_scatter_test.cc
index b1f1e69d3c..e3d4f98dd7 100644
--- a/tensorflow/compiler/xla/tests/select_and_scatter_test.cc
+++ b/tensorflow/compiler/xla/tests/select_and_scatter_test.cc
@@ -22,8 +22,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/local_client.h"
#include "tensorflow/compiler/xla/client/padding.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/layout_util.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/reference_util.h"
diff --git a/tensorflow/compiler/xla/tests/select_test.cc b/tensorflow/compiler/xla/tests/select_test.cc
index 59409ab26e..1c01402798 100644
--- a/tensorflow/compiler/xla/tests/select_test.cc
+++ b/tensorflow/compiler/xla/tests/select_test.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
#include "tensorflow/compiler/xla/tests/test_macros.h"
diff --git a/tensorflow/compiler/xla/tests/slice_test.cc b/tensorflow/compiler/xla/tests/slice_test.cc
index a593faca00..b8ad6668f8 100644
--- a/tensorflow/compiler/xla/tests/slice_test.cc
+++ b/tensorflow/compiler/xla/tests/slice_test.cc
@@ -20,7 +20,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array2d.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/reference_util.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
diff --git a/tensorflow/compiler/xla/tests/test_utils_test.cc b/tensorflow/compiler/xla/tests/test_utils_test.cc
index 8f424ae81f..a2f0338e25 100644
--- a/tensorflow/compiler/xla/tests/test_utils_test.cc
+++ b/tensorflow/compiler/xla/tests/test_utils_test.cc
@@ -15,7 +15,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/tests/test_utils.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/service/hlo_parser.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/tests/local_client_test_base.h"
diff --git a/tensorflow/compiler/xla/tests/transfer_manager_test.cc b/tensorflow/compiler/xla/tests/transfer_manager_test.cc
index 0f86b7f20f..125513ddfd 100644
--- a/tensorflow/compiler/xla/tests/transfer_manager_test.cc
+++ b/tensorflow/compiler/xla/tests/transfer_manager_test.cc
@@ -22,6 +22,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/service/device_memory_allocator.h"
#include "tensorflow/compiler/xla/service/generic_transfer_manager.h"
#include "tensorflow/compiler/xla/service/shaped_buffer.h"
+#include "tensorflow/compiler/xla/service/stream_pool.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
@@ -60,7 +61,7 @@ class TransferManagerTest : public LocalClientTestBase {
}
protected:
- Backend::StreamPtr stream_ptr_;
+ StreamPool::Ptr stream_ptr_;
se::Stream* stream_;
private:
diff --git a/tensorflow/compiler/xla/tests/transpose_test.cc b/tensorflow/compiler/xla/tests/transpose_test.cc
index 6ebb4324f8..fbe9d1b64a 100644
--- a/tensorflow/compiler/xla/tests/transpose_test.cc
+++ b/tensorflow/compiler/xla/tests/transpose_test.cc
@@ -17,7 +17,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array2d.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/reference_util.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/hlo_test_base.h"
diff --git a/tensorflow/compiler/xla/tests/tuple_test.cc b/tensorflow/compiler/xla/tests/tuple_test.cc
index a517007591..2fd70b72b5 100644
--- a/tensorflow/compiler/xla/tests/tuple_test.cc
+++ b/tensorflow/compiler/xla/tests/tuple_test.cc
@@ -18,8 +18,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/array2d.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal_util.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/statusor.h"
diff --git a/tensorflow/compiler/xla/tests/unary_op_test.cc b/tensorflow/compiler/xla/tests/unary_op_test.cc
index a90a6fb0a5..20ae68ab74 100644
--- a/tensorflow/compiler/xla/tests/unary_op_test.cc
+++ b/tensorflow/compiler/xla/tests/unary_op_test.cc
@@ -18,7 +18,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
#include "tensorflow/compiler/xla/tests/test_macros.h"
diff --git a/tensorflow/compiler/xla/tests/vector_ops_reduce_test.cc b/tensorflow/compiler/xla/tests/vector_ops_reduce_test.cc
index ea3aba6df1..ef1b1445bb 100644
--- a/tensorflow/compiler/xla/tests/vector_ops_reduce_test.cc
+++ b/tensorflow/compiler/xla/tests/vector_ops_reduce_test.cc
@@ -21,7 +21,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/array3d.h"
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/literal_test_util.h"
#include "tensorflow/compiler/xla/tests/test_macros.h"
diff --git a/tensorflow/compiler/xla/tests/vector_ops_simple_test.cc b/tensorflow/compiler/xla/tests/vector_ops_simple_test.cc
index 79bae22dac..3848ec1684 100644
--- a/tensorflow/compiler/xla/tests/vector_ops_simple_test.cc
+++ b/tensorflow/compiler/xla/tests/vector_ops_simple_test.cc
@@ -21,8 +21,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/statusor.h"
#include "tensorflow/compiler/xla/test_helpers.h"
diff --git a/tensorflow/compiler/xla/tests/while_test.cc b/tensorflow/compiler/xla/tests/while_test.cc
index 29befef92e..c81c27891c 100644
--- a/tensorflow/compiler/xla/tests/while_test.cc
+++ b/tensorflow/compiler/xla/tests/while_test.cc
@@ -20,8 +20,8 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/client_library.h"
#include "tensorflow/compiler/xla/client/lib/arithmetic.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/service/platform_util.h"
#include "tensorflow/compiler/xla/shape_util.h"
diff --git a/tensorflow/compiler/xla/tests/xla_hlo_profile_test.cc b/tensorflow/compiler/xla/tests/xla_hlo_profile_test.cc
index c000ff4dc8..0ee8e68c88 100644
--- a/tensorflow/compiler/xla/tests/xla_hlo_profile_test.cc
+++ b/tensorflow/compiler/xla/tests/xla_hlo_profile_test.cc
@@ -18,10 +18,11 @@ limitations under the License.
#include "tensorflow/compiler/xla/array2d.h"
#include "tensorflow/compiler/xla/client/local_client.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_builder.h"
-#include "tensorflow/compiler/xla/client/xla_client/xla_computation.h"
+#include "tensorflow/compiler/xla/client/xla_builder.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/map_util.h"
#include "tensorflow/compiler/xla/service/platform_util.h"
+#include "tensorflow/compiler/xla/service/stream_pool.h"
#include "tensorflow/compiler/xla/shape_util.h"
#include "tensorflow/compiler/xla/tests/client_library_test_base.h"
#include "tensorflow/compiler/xla/tests/test_macros.h"
@@ -133,7 +134,7 @@ void ExecuteAndFetchProfile(string* profile_output, LocalClient* client,
DeviceMemoryAllocator* allocator = backend->memory_allocator();
auto* transfer_manager = backend->transfer_manager();
TF_ASSERT_OK_AND_ASSIGN(
- Backend::StreamPtr stream_ptr,
+ StreamPool::Ptr stream_ptr,
backend->BorrowStream(backend->default_device_ordinal()));
TF_ASSERT_OK_AND_ASSIGN(
diff --git a/tensorflow/compiler/xla/tools/BUILD b/tensorflow/compiler/xla/tools/BUILD
index 55501827f2..d7cabbe876 100644
--- a/tensorflow/compiler/xla/tools/BUILD
+++ b/tensorflow/compiler/xla/tools/BUILD
@@ -37,6 +37,7 @@ cc_library(
"//tensorflow/compiler/xla/client",
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/legacy_flags:debug_options_flags",
"//tensorflow/compiler/xla/service",
"//tensorflow/compiler/xla/service:hlo_proto",
@@ -84,6 +85,7 @@ cc_library(
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:global_data",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/client/lib:testing",
"//tensorflow/compiler/xla/service:hlo_parser",
"//tensorflow/compiler/xla/service:hlo_proto",
@@ -164,6 +166,7 @@ tf_cc_binary(
"//tensorflow/compiler/xla/client",
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/service",
"//tensorflow/compiler/xla/service:hlo_proto",
"//tensorflow/compiler/xla/service:interpreter_plugin",
@@ -181,6 +184,7 @@ tf_cc_binary(
"//tensorflow/compiler/xla/client",
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/service",
"//tensorflow/compiler/xla/service:hlo",
"//tensorflow/compiler/xla/service:hlo_proto",
@@ -198,6 +202,7 @@ tf_cc_binary(
"//tensorflow/compiler/xla/client",
"//tensorflow/compiler/xla/client:client_library",
"//tensorflow/compiler/xla/client:local_client",
+ "//tensorflow/compiler/xla/client:xla_computation",
"//tensorflow/compiler/xla/legacy_flags:debug_options_flags",
"//tensorflow/compiler/xla/service",
"//tensorflow/compiler/xla/service:hlo_graph_dumper",
diff --git a/tensorflow/compiler/xla/tools/dumped_computation_to_graphviz.cc b/tensorflow/compiler/xla/tools/dumped_computation_to_graphviz.cc
index befb554537..f20dcef382 100644
--- a/tensorflow/compiler/xla/tools/dumped_computation_to_graphviz.cc
+++ b/tensorflow/compiler/xla/tools/dumped_computation_to_graphviz.cc
@@ -31,6 +31,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/client.h"
#include "tensorflow/compiler/xla/client/client_library.h"
#include "tensorflow/compiler/xla/client/local_client.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/legacy_flags/debug_options_flags.h"
#include "tensorflow/compiler/xla/service/hlo.pb.h"
#include "tensorflow/compiler/xla/service/service.h"
diff --git a/tensorflow/compiler/xla/tools/dumped_computation_to_operation_list.cc b/tensorflow/compiler/xla/tools/dumped_computation_to_operation_list.cc
index cfb8f37487..f0af0580c1 100644
--- a/tensorflow/compiler/xla/tools/dumped_computation_to_operation_list.cc
+++ b/tensorflow/compiler/xla/tools/dumped_computation_to_operation_list.cc
@@ -22,6 +22,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/client.h"
#include "tensorflow/compiler/xla/client/client_library.h"
#include "tensorflow/compiler/xla/client/local_client.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/service/dfs_hlo_visitor_with_default.h"
#include "tensorflow/compiler/xla/service/hlo.pb.h"
#include "tensorflow/compiler/xla/service/service.h"
diff --git a/tensorflow/compiler/xla/tools/dumped_computation_to_text.cc b/tensorflow/compiler/xla/tools/dumped_computation_to_text.cc
index 5dd5150be3..f03e1b1f96 100644
--- a/tensorflow/compiler/xla/tools/dumped_computation_to_text.cc
+++ b/tensorflow/compiler/xla/tools/dumped_computation_to_text.cc
@@ -20,6 +20,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/client.h"
#include "tensorflow/compiler/xla/client/client_library.h"
#include "tensorflow/compiler/xla/client/local_client.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/service/hlo.pb.h"
#include "tensorflow/compiler/xla/service/service.h"
#include "tensorflow/compiler/xla/statusor.h"
diff --git a/tensorflow/compiler/xla/tools/dumped_computation_to_tf_graphdef.cc b/tensorflow/compiler/xla/tools/dumped_computation_to_tf_graphdef.cc
index a5dce20456..dc5c106d02 100644
--- a/tensorflow/compiler/xla/tools/dumped_computation_to_tf_graphdef.cc
+++ b/tensorflow/compiler/xla/tools/dumped_computation_to_tf_graphdef.cc
@@ -29,6 +29,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/client.h"
#include "tensorflow/compiler/xla/client/client_library.h"
#include "tensorflow/compiler/xla/client/local_client.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/legacy_flags/debug_options_flags.h"
#include "tensorflow/compiler/xla/service/hlo.pb.h"
#include "tensorflow/compiler/xla/service/service.h"
diff --git a/tensorflow/compiler/xla/tools/replay_computation.cc b/tensorflow/compiler/xla/tools/replay_computation.cc
index 854e797ec2..3bb2f3c000 100644
--- a/tensorflow/compiler/xla/tools/replay_computation.cc
+++ b/tensorflow/compiler/xla/tools/replay_computation.cc
@@ -42,6 +42,7 @@ limitations under the License.
#include "tensorflow/compiler/xla/client/global_data.h"
#include "tensorflow/compiler/xla/client/lib/testing.h"
#include "tensorflow/compiler/xla/client/local_client.h"
+#include "tensorflow/compiler/xla/client/xla_computation.h"
#include "tensorflow/compiler/xla/execution_options_util.h"
#include "tensorflow/compiler/xla/literal.h"
#include "tensorflow/compiler/xla/service/gpu/infeed_manager.h"
diff --git a/tensorflow/contrib/BUILD b/tensorflow/contrib/BUILD
index a173c51879..6a4e252b44 100644
--- a/tensorflow/contrib/BUILD
+++ b/tensorflow/contrib/BUILD
@@ -130,8 +130,12 @@ py_library(
"//tensorflow/contrib/bigtable", # depends on bigtable
"//tensorflow/contrib/cloud:cloud_py", # doesn't compile on Windows
"//tensorflow/contrib/ffmpeg:ffmpeg_ops_py",
- "//tensorflow/contrib/lite/python:lite", # unix dependency, need to fix code
+ # TODO(aaroey): tensorrt dependency has to appear before tflite so the
+ # build can resolve its flatbuffers symbols within the tensorrt library.
+ # This is an issue with the tensorrt static library and will be fixed by
+ # the next tensorrt release, so fix the order here after that.
"//tensorflow/contrib/tensorrt:init_py", # doesn't compile on windows
+ "//tensorflow/contrib/lite/python:lite", # unix dependency, need to fix code
]),
)
diff --git a/tensorflow/contrib/autograph/converters/asserts_test.py b/tensorflow/contrib/autograph/converters/asserts_test.py
index 9c58ae3acc..38faba45df 100644
--- a/tensorflow/contrib/autograph/converters/asserts_test.py
+++ b/tensorflow/contrib/autograph/converters/asserts_test.py
@@ -35,7 +35,7 @@ class AssertsTest(converter_testing.TestCase):
node, ctx = self.prepare(test_fn, {})
node = asserts.transform(node, ctx)
- self.assertTrue(isinstance(node.body[0].body[0].value, gast.Call))
+ self.assertTrue(isinstance(node.body[0].value, gast.Call))
if __name__ == '__main__':
diff --git a/tensorflow/contrib/autograph/converters/break_statements.py b/tensorflow/contrib/autograph/converters/break_statements.py
index 2a60750bda..180779670d 100644
--- a/tensorflow/contrib/autograph/converters/break_statements.py
+++ b/tensorflow/contrib/autograph/converters/break_statements.py
@@ -42,7 +42,7 @@ class BreakTransformer(converter.Base):
var_name = self.state[_Break].control_var_name
# TODO(mdan): This will fail when expanded inside a top-level else block.
template = """
- var_name = True
+ var_name = tf.constant(True)
continue
"""
return templates.replace(template, var_name=var_name)
@@ -85,7 +85,7 @@ class BreakTransformer(converter.Base):
guarded_orelse = self._guard_if_present(node.orelse, break_var)
template = """
- var_name = False
+ var_name = tf.constant(False)
while test and not var_name:
body
else:
@@ -122,7 +122,7 @@ class BreakTransformer(converter.Base):
# the control variable is marked as used.
# TODO(mdan): Use a marker instead, e.g. ag__.condition_loop_on(var_name)
template = """
- var_name = False
+ var_name = tf.constant(False)
for target in iter_:
(var_name,)
body
diff --git a/tensorflow/contrib/autograph/converters/break_statements_test.py b/tensorflow/contrib/autograph/converters/break_statements_test.py
index c26ca2946c..fcae7d68c0 100644
--- a/tensorflow/contrib/autograph/converters/break_statements_test.py
+++ b/tensorflow/contrib/autograph/converters/break_statements_test.py
@@ -20,13 +20,16 @@ from __future__ import print_function
from tensorflow.contrib.autograph.converters import break_statements
from tensorflow.contrib.autograph.core import converter_testing
+from tensorflow.python.eager import context as tfe_ctx
+from tensorflow.python.framework import constant_op
from tensorflow.python.platform import test
class BreakCanonicalizationTest(converter_testing.TestCase):
def assertTransformedEquivalent(self, test_fn, *inputs):
- with self.converted(test_fn, break_statements, {}) as result:
+ with self.converted(test_fn, break_statements, {},
+ constant_op.constant) as result:
self.assertEqual(test_fn(*inputs), result.test_fn(*inputs))
def test_while_loop(self):
@@ -40,9 +43,10 @@ class BreakCanonicalizationTest(converter_testing.TestCase):
v.append(x)
return v
- self.assertTransformedEquivalent(test_fn, 0)
- self.assertTransformedEquivalent(test_fn, 1)
- self.assertTransformedEquivalent(test_fn, 4)
+ with tfe_ctx.eager_mode():
+ self.assertTransformedEquivalent(test_fn, 0)
+ self.assertTransformedEquivalent(test_fn, 1)
+ self.assertTransformedEquivalent(test_fn, 4)
def test_for_loop(self):
@@ -55,7 +59,8 @@ class BreakCanonicalizationTest(converter_testing.TestCase):
v.append(x)
return v
- with self.converted(test_fn, break_statements, {}) as result:
+ with self.converted(test_fn, break_statements, {},
+ constant_op.constant) as result:
# The break is incompletely canonicalized. The loop will not interrupt,
# but the section following the break will be skipped.
self.assertEqual([3], result.test_fn([5, 4]))
@@ -77,9 +82,10 @@ class BreakCanonicalizationTest(converter_testing.TestCase):
v.append(x)
return v, u, w
- self.assertTransformedEquivalent(test_fn, 0)
- self.assertTransformedEquivalent(test_fn, 3)
- self.assertTransformedEquivalent(test_fn, 11)
+ with tfe_ctx.eager_mode():
+ self.assertTransformedEquivalent(test_fn, 0)
+ self.assertTransformedEquivalent(test_fn, 3)
+ self.assertTransformedEquivalent(test_fn, 11)
def test_nested_loops(self):
@@ -99,10 +105,11 @@ class BreakCanonicalizationTest(converter_testing.TestCase):
v.append(x)
return v, u
- self.assertTransformedEquivalent(test_fn, 0)
- self.assertTransformedEquivalent(test_fn, 2)
- self.assertTransformedEquivalent(test_fn, 3)
- self.assertTransformedEquivalent(test_fn, 5)
+ with tfe_ctx.eager_mode():
+ self.assertTransformedEquivalent(test_fn, 0)
+ self.assertTransformedEquivalent(test_fn, 2)
+ self.assertTransformedEquivalent(test_fn, 3)
+ self.assertTransformedEquivalent(test_fn, 5)
def test_loop_orelse(self):
@@ -120,9 +127,10 @@ class BreakCanonicalizationTest(converter_testing.TestCase):
v.append(x)
return v, u
- self.assertTransformedEquivalent(test_fn, 0)
- self.assertTransformedEquivalent(test_fn, 2)
- self.assertTransformedEquivalent(test_fn, 3)
+ with tfe_ctx.eager_mode():
+ self.assertTransformedEquivalent(test_fn, 0)
+ self.assertTransformedEquivalent(test_fn, 2)
+ self.assertTransformedEquivalent(test_fn, 3)
if __name__ == '__main__':
diff --git a/tensorflow/contrib/autograph/converters/continue_statements.py b/tensorflow/contrib/autograph/converters/continue_statements.py
index 958bde0a58..0476e97c15 100644
--- a/tensorflow/contrib/autograph/converters/continue_statements.py
+++ b/tensorflow/contrib/autograph/converters/continue_statements.py
@@ -37,7 +37,7 @@ class ContinueCanonicalizationTransformer(converter.Base):
def visit_Continue(self, node):
self.set_local(CONTINUE_USED, True)
template = """
- var_name = True
+ var_name = tf.constant(True)
"""
return templates.replace(
template, var_name=self.get_local(CONTROL_VAR_NAME))
@@ -92,7 +92,7 @@ class ContinueCanonicalizationTransformer(converter.Base):
if self.get_local(CONTINUE_USED, False):
template = """
- var_name = False
+ var_name = tf.constant(False)
"""
control_var_init = templates.replace(template, var_name=continue_var)
nodes = control_var_init + nodes
diff --git a/tensorflow/contrib/autograph/converters/continue_statements_test.py b/tensorflow/contrib/autograph/converters/continue_statements_test.py
index 3a7c7d1486..37c15211b4 100644
--- a/tensorflow/contrib/autograph/converters/continue_statements_test.py
+++ b/tensorflow/contrib/autograph/converters/continue_statements_test.py
@@ -20,13 +20,16 @@ from __future__ import print_function
from tensorflow.contrib.autograph.converters import continue_statements
from tensorflow.contrib.autograph.core import converter_testing
+from tensorflow.python.eager import context as tfe_ctx
+from tensorflow.python.framework import constant_op
from tensorflow.python.platform import test
class ContinueCanonicalizationTest(converter_testing.TestCase):
def assertTransformedEquivalent(self, test_fn, *inputs):
- with self.converted(test_fn, continue_statements, {}) as result:
+ with self.converted(test_fn, continue_statements, {},
+ constant_op.constant) as result:
self.assertEqual(test_fn(*inputs), result.test_fn(*inputs))
def test_basic(self):
@@ -40,10 +43,11 @@ class ContinueCanonicalizationTest(converter_testing.TestCase):
v.append(x)
return v
- self.assertTransformedEquivalent(test_fn, 0)
- self.assertTransformedEquivalent(test_fn, 1)
- self.assertTransformedEquivalent(test_fn, 3)
- self.assertTransformedEquivalent(test_fn, 4)
+ with tfe_ctx.eager_mode():
+ self.assertTransformedEquivalent(test_fn, 0)
+ self.assertTransformedEquivalent(test_fn, 1)
+ self.assertTransformedEquivalent(test_fn, 3)
+ self.assertTransformedEquivalent(test_fn, 4)
def test_for_loop(self):
@@ -56,10 +60,11 @@ class ContinueCanonicalizationTest(converter_testing.TestCase):
v.append(x)
return v
- self.assertTransformedEquivalent(test_fn, [])
- self.assertTransformedEquivalent(test_fn, [1])
- self.assertTransformedEquivalent(test_fn, [2])
- self.assertTransformedEquivalent(test_fn, [1, 2, 3])
+ with tfe_ctx.eager_mode():
+ self.assertTransformedEquivalent(test_fn, [])
+ self.assertTransformedEquivalent(test_fn, [1])
+ self.assertTransformedEquivalent(test_fn, [2])
+ self.assertTransformedEquivalent(test_fn, [1, 2, 3])
def test_nested(self):
@@ -78,10 +83,11 @@ class ContinueCanonicalizationTest(converter_testing.TestCase):
v.append(x)
return v, u, w
- self.assertTransformedEquivalent(test_fn, 0)
- self.assertTransformedEquivalent(test_fn, 1)
- self.assertTransformedEquivalent(test_fn, 3)
- self.assertTransformedEquivalent(test_fn, 4)
+ with tfe_ctx.eager_mode():
+ self.assertTransformedEquivalent(test_fn, 0)
+ self.assertTransformedEquivalent(test_fn, 1)
+ self.assertTransformedEquivalent(test_fn, 3)
+ self.assertTransformedEquivalent(test_fn, 4)
if __name__ == '__main__':
diff --git a/tensorflow/contrib/autograph/converters/directives_test.py b/tensorflow/contrib/autograph/converters/directives_test.py
index 5f798a5b76..a573ba5850 100644
--- a/tensorflow/contrib/autograph/converters/directives_test.py
+++ b/tensorflow/contrib/autograph/converters/directives_test.py
@@ -38,7 +38,7 @@ class DirectivesTest(converter_testing.TestCase):
node, ctx = self.prepare(test_fn, {'directives': directives})
node = directives_converter.transform(node, ctx)
- def_, = anno.getanno(node.body[0].body[0].targets[0],
+ def_, = anno.getanno(node.body[0].targets[0],
anno.Static.DEFINITIONS)
d = def_.directives[directives.set_element_type]
self.assertEqual(d['dtype'].s, 'a')
@@ -52,7 +52,7 @@ class DirectivesTest(converter_testing.TestCase):
node, ctx = self.prepare(test_fn, {'directives': directives})
node = directives_converter.transform(node, ctx)
- def_, = anno.getanno(node.body[0].args.args[0], anno.Static.DEFINITIONS)
+ def_, = anno.getanno(node.args.args[0], anno.Static.DEFINITIONS)
d = def_.directives[directives.set_element_type]
self.assertEqual(d['dtype'].n, 1)
self.assertEqual(d['shape'].n, 2)
@@ -67,7 +67,7 @@ class DirectivesTest(converter_testing.TestCase):
node, ctx = self.prepare(test_fn, {'directives': directives})
node = directives_converter.transform(node, ctx)
- d = anno.getanno(node.body[0].body[1], AgAnno.DIRECTIVES)
+ d = anno.getanno(node.body[1], AgAnno.DIRECTIVES)
d = d[directives.set_loop_options]
self.assertEqual(d['parallel_iterations'].n, 10)
self.assertEqual(d['back_prop'].id, 'a')
diff --git a/tensorflow/contrib/autograph/converters/error_handlers.py b/tensorflow/contrib/autograph/converters/error_handlers.py
index 3f23662152..1936821394 100644
--- a/tensorflow/contrib/autograph/converters/error_handlers.py
+++ b/tensorflow/contrib/autograph/converters/error_handlers.py
@@ -37,7 +37,8 @@ class ErrorRewritingTransformer(converter.Base):
def visit_FunctionDef(self, node):
node = self.generic_visit(node)
- if anno.hasanno(node, anno.Basic.ORIGIN):
+ if (anno.hasanno(node, anno.Basic.ORIGIN) and
+ len(self.enclosing_entities) <= 1):
template = """
try:
body
diff --git a/tensorflow/contrib/autograph/converters/error_handlers_test.py b/tensorflow/contrib/autograph/converters/error_handlers_test.py
index 878526c8b4..cd74e5f18f 100644
--- a/tensorflow/contrib/autograph/converters/error_handlers_test.py
+++ b/tensorflow/contrib/autograph/converters/error_handlers_test.py
@@ -34,11 +34,13 @@ class ErrorHandlersTest(converter_testing.TestCase):
raise ValueError()
node, ctx = self.prepare(test_fn, {})
- anno.setanno(node.body[0], anno.Basic.ORIGIN,
- origin_info.OriginInfo('test_path', None, None, None, None))
+ anno.setanno(node, anno.Basic.ORIGIN,
+ origin_info.OriginInfo(None, None, None))
node = error_handlers.transform(node, ctx)
with self.compiled(node, {}) as result:
with self.assertRaises(errors.GraphConstructionError):
+ # Here we just assert that the handler works. Its correctness is
+ # verified by errors_test.py.
result.test_fn()
def test_no_origin_annotation(self):
diff --git a/tensorflow/contrib/autograph/converters/lists_test.py b/tensorflow/contrib/autograph/converters/lists_test.py
index f906918ac0..996e99ee61 100644
--- a/tensorflow/contrib/autograph/converters/lists_test.py
+++ b/tensorflow/contrib/autograph/converters/lists_test.py
@@ -79,7 +79,7 @@ class ListTest(converter_testing.TestCase):
ns = {'special_functions': special_functions}
node, ctx = self.prepare(test_fn, ns)
- def_, = anno.getanno(node.body[0].body[0].targets[0],
+ def_, = anno.getanno(node.body[0].targets[0],
anno.Static.ORIG_DEFINITIONS)
def_.directives[directives.set_element_type] = {
'dtype': parser.parse_expression('tf.int32'),
@@ -114,7 +114,7 @@ class ListTest(converter_testing.TestCase):
return tf.stack(l)
node, ctx = self.prepare(test_fn, {})
- def_, = anno.getanno(node.body[0].body[0].targets[0],
+ def_, = anno.getanno(node.body[0].targets[0],
anno.Static.ORIG_DEFINITIONS)
def_.directives[directives.set_element_type] = {
'dtype': parser.parse_expression('tf.int32')
diff --git a/tensorflow/contrib/autograph/converters/side_effect_guards_test.py b/tensorflow/contrib/autograph/converters/side_effect_guards_test.py
index de1874321e..bee512abbc 100644
--- a/tensorflow/contrib/autograph/converters/side_effect_guards_test.py
+++ b/tensorflow/contrib/autograph/converters/side_effect_guards_test.py
@@ -43,7 +43,7 @@ class SideEffectGuardsTest(converter_testing.TestCase):
node, ctx = self.prepare(test_fn, {})
node = side_effect_guards.transform(node, ctx)
- self.assertEqual(len(node.body[0].body), 1)
+ self.assertEqual(len(node.body), 1)
with self.compiled(node, {}, state_ops.assign) as result:
with self.test_session() as sess:
@@ -64,7 +64,7 @@ class SideEffectGuardsTest(converter_testing.TestCase):
node, ctx = self.prepare(test_fn, {})
node = side_effect_guards.transform(node, ctx)
- self.assertEqual(len(node.body[0].body), 1)
+ self.assertEqual(len(node.body), 1)
with self.compiled(node, {}, state_ops.assign) as result:
with self.test_session() as sess:
@@ -84,7 +84,7 @@ class SideEffectGuardsTest(converter_testing.TestCase):
node, ctx = self.prepare(test_fn, {})
node = side_effect_guards.transform(node, ctx)
- self.assertEqual(len(node.body[0].body), 1)
+ self.assertEqual(len(node.body), 1)
with self.compiled(node, {}, control_flow_ops.Assert) as result:
with self.test_session() as sess:
@@ -104,7 +104,7 @@ class SideEffectGuardsTest(converter_testing.TestCase):
node, ctx = self.prepare(test_fn, {})
node = side_effect_guards.transform(node, ctx)
- self.assertEqual(len(node.body[0].body), 1)
+ self.assertEqual(len(node.body), 1)
with self.compiled(node, {}, state_ops.assign_add) as result:
with self.test_session() as sess:
@@ -125,7 +125,7 @@ class SideEffectGuardsTest(converter_testing.TestCase):
node, ctx = self.prepare(test_fn, {})
node = side_effect_guards.transform(node, ctx)
- self.assertEqual(len(node.body[0].body[0].body), 1)
+ self.assertEqual(len(node.body[0].body), 1)
with self.compiled(node, {}, state_ops.assign, ops.name_scope) as result:
with self.test_session() as sess:
@@ -147,7 +147,7 @@ class SideEffectGuardsTest(converter_testing.TestCase):
node, ctx = self.prepare(test_fn, {})
node = side_effect_guards.transform(node, ctx)
- self.assertEqual(len(node.body[0].body), 1)
+ self.assertEqual(len(node.body), 1)
with self.compiled(node, {}, state_ops.assign,
state_ops.assign_add) as result:
diff --git a/tensorflow/contrib/autograph/converters/slices.py b/tensorflow/contrib/autograph/converters/slices.py
index 9cfa066672..c527f98613 100644
--- a/tensorflow/contrib/autograph/converters/slices.py
+++ b/tensorflow/contrib/autograph/converters/slices.py
@@ -36,12 +36,14 @@ class SliceTransformer(converter.Base):
def _process_single_assignment(self, target, value):
if not isinstance(target, gast.Subscript):
return None
+ if not isinstance(target.slice, gast.Index):
+ return None
template = """
target = ag__.set_item(target, key, item)
"""
return templates.replace(
- template, target=target.value, key=target.slice, item=value)
+ template, target=target.value, key=target.slice.value, item=value)
def visit_Assign(self, node):
node = self.generic_visit(node)
@@ -76,7 +78,7 @@ class SliceTransformer(converter.Base):
opts=ag__.GetItemOpts(element_dtype=dtype))
"""
return templates.replace_as_expression(
- template, target=node.value, key=node.slice, dtype=dtype)
+ template, target=node.value, key=node.slice.value, dtype=dtype)
def transform(node, ctx):
diff --git a/tensorflow/contrib/autograph/converters/slices_test.py b/tensorflow/contrib/autograph/converters/slices_test.py
index 3c0f81e8bc..c822d53a4a 100644
--- a/tensorflow/contrib/autograph/converters/slices_test.py
+++ b/tensorflow/contrib/autograph/converters/slices_test.py
@@ -38,7 +38,7 @@ class SliceTest(converter_testing.TestCase):
return l[1]
node, ctx = self.prepare(test_fn, {})
- def_, = anno.getanno(node.body[0].args.args[0], anno.Static.DEFINITIONS)
+ def_, = anno.getanno(node.args.args[0], anno.Static.DEFINITIONS)
def_.directives[directives.set_element_type] = {
'dtype': parser.parse_expression('tf.int32')
}
@@ -59,11 +59,11 @@ class SliceTest(converter_testing.TestCase):
return l[1]
node, ctx = self.prepare(test_fn, {})
- def_, = anno.getanno(node.body[0].args.args[0], anno.Static.DEFINITIONS)
+ def_, = anno.getanno(node.args.args[0], anno.Static.DEFINITIONS)
def_.directives[directives.set_element_type] = {
'dtype': parser.parse_expression('tf.int32')
}
- def_, = anno.getanno(node.body[0].body[0].body[0].targets[0],
+ def_, = anno.getanno(node.body[0].body[0].targets[0],
anno.Static.DEFINITIONS)
def_.directives[directives.set_element_type] = {
'dtype': parser.parse_expression('tf.float32')
diff --git a/tensorflow/contrib/autograph/core/converter_testing.py b/tensorflow/contrib/autograph/core/converter_testing.py
index 2025e32817..5ee2c3fffd 100644
--- a/tensorflow/contrib/autograph/core/converter_testing.py
+++ b/tensorflow/contrib/autograph/core/converter_testing.py
@@ -94,7 +94,8 @@ class TestCase(test.TestCase):
return 7
try:
- result, source = compiler.ast_to_object(node)
+ result, source = compiler.ast_to_object(node, include_source_map=True)
+
result.tf = self.make_fake_mod('fake_tf', *symbols)
fake_ag = self.make_fake_mod('fake_ag', converted_call)
fake_ag.__dict__.update(operators.__dict__)
@@ -144,6 +145,7 @@ class TestCase(test.TestCase):
recursive=True,
autograph_decorators=()):
node, source = parser.parse_entity(test_fn)
+ node = node.body[0]
if namer is None:
namer = FakeNamer()
program_ctx = converter.ProgramContext(
diff --git a/tensorflow/contrib/autograph/core/errors.py b/tensorflow/contrib/autograph/core/errors.py
index e58745337a..5a57d57e7d 100644
--- a/tensorflow/contrib/autograph/core/errors.py
+++ b/tensorflow/contrib/autograph/core/errors.py
@@ -31,9 +31,10 @@ import logging
import sys
import traceback
-from tensorflow.contrib.autograph.pyct.origin_info import CodeLocation
+from tensorflow.contrib.autograph.pyct import origin_info
from tensorflow.python.framework import errors_impl
-from tensorflow.python.util import tf_inspect
+
+# TODO(mdan): Add a superclass common to all errors.
class GraphConstructionError(Exception):
@@ -65,51 +66,43 @@ class TfRuntimeError(Exception):
return message + ''.join(traceback.format_list(self.custom_traceback))
-def _rewrite_frame(source_map, cleaned_traceback, stack_frame_indices):
- """Rewrites the stack frames at the given indices using the given source map.
+def _rewrite_tb(source_map, tb):
+ """Rewrites code references in a traceback.
Args:
- source_map: Dict[CodeLocation, OriginInfo], a mapping between the user and
- AG generated code.
- cleaned_traceback: List[Tuple[text, text, text, text]], the current
- traceback.
- stack_frame_indices: Iterable[Int], frame indices to possibly rewrite if
- there are matching source mapping keys.
-
+ source_map: Dict[origin_info.LineLocation, origin_info.OriginInfo], mapping
+ locations to their origin
+ tb: List[Tuple[Text, Text, Text, Text]], consistent with
+ traceback.extract_tb.
Returns:
- None
+ List[Tuple[Text, Text, Text, Text]], the rewritten traceback
"""
- for frame_index in stack_frame_indices:
- # (file_path, line number, function name, code)
- file_path, line_number, _, _ = cleaned_traceback[frame_index]
- source_map_key = CodeLocation(file_path=file_path, line_number=line_number)
- found_mapping = source_map_key in source_map
- if found_mapping:
- cleaned_traceback[frame_index] = source_map[source_map_key].as_frame()
-
-
-# TODO(znado): Make more robust to name changes in the rewriting logic.
-def _remove_rewrite_frames(tb):
- """Remove stack frames containing the error rewriting logic."""
- cleaned_tb = []
- for f in tb:
- if 'ag__.rewrite_graph_construction_error' not in f[3]:
- cleaned_tb.append(f)
- return cleaned_tb
+ new_tb = []
+ for frame in tb:
+ filename, lineno, _, _ = frame
+ loc = origin_info.LineLocation(filename, lineno)
+ origin = source_map.get(loc)
+ if origin is not None:
+ new_tb.append(origin.as_frame())
+ else:
+ new_tb.append(frame)
+ return new_tb
+# TODO(mdan): rename to raise_*
def rewrite_graph_construction_error(source_map):
"""Rewrites errors raised by non-AG APIs inside AG generated code.
- Meant to be called from the try/except block inside each AutoGraph generated
- function. Only rewrites the traceback frames corresponding to the function
- that this is called from. When we raise a GraphConstructionError at the end
- it is then caught by calling functions, where they can be responsible for
- rewriting their own frames.
+ This is called from the except handler inside an AutoGraph generated function
+ (that is, during exception handling). Only rewrites the frames corresponding
+ to the function that this is called from, so each function is responsible
+ to call this to have its own frames rewritten.
+
+ This function always raises an error.
Args:
- source_map: Dict[CodeLocation, OriginInfo], a mapping between the user and
- AG generated code.
+ source_map: Dict[origin_info.Location, origin_info.OriginInfo], the source
+ map belonging to the calling function
Raises:
GraphConstructionError: The rewritten underlying error.
@@ -119,32 +112,17 @@ def rewrite_graph_construction_error(source_map):
_, original_error, e_traceback = error_info
assert original_error is not None
try:
- _, _, _, func_name, _, _ = tf_inspect.stack()[1]
- # The latest function call is added to the beginning of a traceback, but
- # when rewriting the traceback of multiple function calls the previous
- # functions' except blocks may have already rewritten their own frames so
- # we want to copy over all of the previous frames. We may have rewritten
- # previous frames only if the error is a GraphConstructionError.
+ current_traceback = _cut_traceback_loops(source_map,
+ traceback.extract_tb(e_traceback))
if isinstance(original_error, GraphConstructionError):
- cleaned_traceback = traceback.extract_tb(e_traceback)
+ # TODO(mdan): This is incomplete.
+ # The error might have bubbled through a non-converted function.
previous_traceback = original_error.custom_traceback
- cleaned_traceback = [cleaned_traceback[0]] + previous_traceback
+ cleaned_traceback = [current_traceback[0]] + previous_traceback
else:
- cleaned_traceback = traceback.extract_tb(e_traceback)
- cleaned_traceback = _remove_rewrite_frames(cleaned_traceback)
-
- current_frame_indices = []
- # This code is meant to be called from the try/except block that wraps a
- # function body. Here we look for all frames that came from the function
- # that this wraps, look for any matching line numbers in the source
- # mapping, and then rewrite them if matches are found.
- for fi, frame in enumerate(cleaned_traceback):
- _, _, frame_func_name, _ = frame
- if frame_func_name == func_name:
- current_frame_indices.append(fi)
- break
- if current_frame_indices:
- _rewrite_frame(source_map, cleaned_traceback, current_frame_indices)
+ cleaned_traceback = current_traceback
+
+ cleaned_traceback = _rewrite_tb(source_map, cleaned_traceback)
if isinstance(original_error, GraphConstructionError):
original_error.custom_traceback = cleaned_traceback
@@ -153,6 +131,7 @@ def rewrite_graph_construction_error(source_map):
new_error = GraphConstructionError(original_error, cleaned_traceback)
except Exception:
logging.exception('Error while rewriting AutoGraph error:')
+ # TODO(mdan): Should reraise here, removing the top frame as well.
raise original_error
else:
raise new_error
@@ -161,70 +140,77 @@ def rewrite_graph_construction_error(source_map):
del e_traceback
+def _cut_traceback_loops(source_map, original_traceback):
+ """Check for cases where we leave a user method and re-enter it.
+
+ This is done by looking at the function names when the filenames are from any
+ files the user code is in. If we find a case where we return to a user method
+ after leaving it then we cut out the frames in between because we assume this
+ means these in between frames are from internal AutoGraph code that shouldn't
+ be included.
+
+ An example of this is:
+
+ File "file1.py", line 57, in my_func
+ ...
+ File "control_flow_ops.py", line 231, in cond
+ ...
+ File "control_flow_ops.py", line 1039, in inner_cond
+ ...
+ File "file1.py", line 68, in my_func
+ ...
+
+ Where we would remove the control_flow_ops.py frames because we re-enter
+ my_func in file1.py.
+
+ The source map keys are (file_path, line_number) so get the set of all user
+ file_paths.
+
+ Args:
+ source_map: Dict[origin_info.LineLocation, origin_info.OriginInfo], mapping
+ locations to their origin
+ original_traceback: List[Tuple[Text, Text, Text, Text]], consistent with
+ traceback.extract_tb.
+
+ Returns:
+ List[Tuple[Text, Text, Text, Text]], the traceback with any loops removed.
+ """
+ all_user_files = set(loc.filename for loc in source_map)
+ cleaned_traceback = []
+ last_user_frame_index = None
+ last_user_user_file_path = None
+ # TODO(mdan): Simplify this logic.
+ for fi, frame in enumerate(original_traceback):
+ frame_file_path, lineno, _, _ = frame
+ src_map_key = origin_info.LineLocation(frame_file_path, lineno)
+ if frame_file_path in all_user_files:
+ if src_map_key in source_map:
+ if (last_user_frame_index is not None and
+ last_user_user_file_path == frame_file_path):
+ cleaned_traceback = cleaned_traceback[:last_user_frame_index]
+ last_user_frame_index = fi
+ last_user_user_file_path = frame_file_path
+ cleaned_traceback.append(frame)
+ return cleaned_traceback
+
+
+# TODO(mdan): This should be consistent with rewrite_graph_construction_error
+# Both should either raise or return.
def rewrite_tf_runtime_error(error, source_map):
"""Rewrites TensorFlow runtime errors raised by ops created in AG code.
Args:
- error: error_impl.OpError, an TensorFlow error that will have its traceback
- rewritten.
- source_map: Dict[CodeLocation, OriginInfo], a mapping between the user and
- AG generated code.
+ error: tf.OpError
+ source_map: Dict[origin_info.LineLocation, origin_info.OriginInfo]
Returns:
- A TfRuntimeError with a traceback rewritten according to the given
- source mapping.
+ TfRuntimeError, the rewritten underlying error.
"""
- # Check for cases where we leave a user method and re-enter it in the
- # traceback. This is done by looking at the function names when the
- # filenames are from any files the user code is in. If we find a case where
- # we return to a user method after leaving it then we cut out the frames in
- # between because we assume this means these in between frames are from
- # internal AutoGraph code that shouldn't be included.
- #
- # An example of this is:
- #
- # File "file1.py", line 57, in my_func
- # ...
- # File "control_flow_ops.py", line 231, in cond
- # ...
- # File "control_flow_ops.py", line 1039, in inner_cond
- # ...
- # File "file1.py", line 68, in my_func
- # ...
- #
- # Where we would remove the control_flow_ops.py frames because we re-enter
- # my_func in file1.py.
- #
- # The source map keys are (file_path, line_number) so get the set of all user
- # file_paths.
try:
- all_user_files = set(k.file_path for k in source_map)
- cleaned_traceback = []
- last_user_frame_index = None
- last_user_user_file_path = None
- last_user_user_fn_name = None
- for fi, frame in enumerate(error.op.traceback):
- frame_file_path, frame_line_number, _, _ = frame
- src_map_key = CodeLocation(
- file_path=frame_file_path, line_number=frame_line_number)
- if frame_file_path in all_user_files:
- if src_map_key in source_map:
- original_fn_name = source_map[src_map_key].function_name
- if (last_user_frame_index is not None and
- last_user_user_file_path == frame_file_path):
- if last_user_user_fn_name == original_fn_name:
- cleaned_traceback = cleaned_traceback[:last_user_frame_index]
- else:
- cleaned_traceback = cleaned_traceback[:last_user_frame_index + 1]
- last_user_user_fn_name = original_fn_name
- else:
- last_user_user_fn_name = None
- last_user_frame_index = fi
- last_user_user_file_path = frame_file_path
- cleaned_traceback.append(frame)
-
- for fi in range(len(cleaned_traceback)):
- _rewrite_frame(source_map, cleaned_traceback, [fi])
+ cleaned_traceback = _cut_traceback_loops(source_map, error.op.traceback)
+ # cleaned_traceback = error.op.traceback
+ cleaned_traceback = _rewrite_tb(source_map, cleaned_traceback)
+
op_name = error.op.name
op_message = error.message
rewritten_error = TfRuntimeError(op_name, op_message, cleaned_traceback)
@@ -263,7 +249,7 @@ def improved_errors(converted_function):
ValueError: If converted_function is not generated by AutoGraph
"""
if (getattr(converted_function, 'ag_source_map', None) is None or
- not converted_function.ag_source_map):
+ not isinstance(converted_function.ag_source_map, dict)):
raise ValueError(
'converted_function must be the result of an autograph.to_graph call')
try:
diff --git a/tensorflow/contrib/autograph/core/errors_test.py b/tensorflow/contrib/autograph/core/errors_test.py
index 7be54563a1..c0e2c74e47 100644
--- a/tensorflow/contrib/autograph/core/errors_test.py
+++ b/tensorflow/contrib/autograph/core/errors_test.py
@@ -28,88 +28,76 @@ from tensorflow.python.util import tf_inspect
def zero_div():
- return array_ops.constant(10, dtype=dtypes.int32) // 0
+ x = array_ops.constant(10, dtype=dtypes.int32)
+ return x // 0
def zero_div_caller():
- a = zero_div() + 2
- return a
+ return zero_div()
class RuntimeErrorsTest(test.TestCase):
- def setUp(self):
- self._fake_origin = origin_info.OriginInfo('new file', 'new func', 96, 0,
- 'print("hello world!")')
-
- def test_error_replacement(self):
- _, zero_div_lineno = tf_inspect.getsourcelines(zero_div)
- src_map = {
- errors.CodeLocation(
- file_path=__file__, line_number=zero_div_lineno + 1):
- self._fake_origin
- }
+ def fake_origin(self, function, line_offset):
+ _, lineno = tf_inspect.getsourcelines(function)
+ filename = tf_inspect.getsourcefile(function)
+ lineno += line_offset
+ loc = origin_info.LineLocation(filename, lineno)
+ origin = origin_info.OriginInfo(loc, 'test_function_name', 'test_code')
+ return loc, origin
+
+ def test_improved_errors_basic(self):
+ loc, origin = self.fake_origin(zero_div, 2)
+ zero_div_caller.ag_source_map = {loc: origin}
+
+ ops = zero_div_caller()
with self.assertRaises(errors.TfRuntimeError) as cm:
- z = zero_div_caller()
- zero_div_caller.ag_source_map = src_map
with errors.improved_errors(zero_div_caller):
with self.test_session() as sess:
- sess.run(z)
- expected = cm.exception
- current_traceback = expected.custom_traceback
- for frame in current_traceback:
- self.assertNotEqual('zero_div', frame[2])
- self.assertTrue(
- any(self._fake_origin.as_frame() == frame
- for frame in current_traceback))
-
- def test_error_not_found(self):
- src_map = {
- errors.CodeLocation(file_path=__file__, line_number=-1):
- self._fake_origin
- }
+ sess.run(ops)
+
+ for frame in cm.exception.custom_traceback:
+ _, _, function_name, _ = frame
+ self.assertNotEqual('zero_div', function_name)
+ self.assertIn(origin.as_frame(), set(cm.exception.custom_traceback))
+
+ def test_improved_errors_no_matching_lineno(self):
+ loc, origin = self.fake_origin(zero_div, -1)
+ zero_div_caller.ag_source_map = {loc: origin}
+
+ ops = zero_div_caller()
with self.assertRaises(errors.TfRuntimeError) as cm:
- z = zero_div_caller()
- zero_div_caller.ag_source_map = src_map
with errors.improved_errors(zero_div_caller):
with self.test_session() as sess:
- sess.run(z)
- expected = cm.exception
- current_traceback = expected.custom_traceback
- self.assertTrue(any('zero_div' in frame[2] for frame in current_traceback))
- for frame in current_traceback:
- self.assertNotEqual(frame, self._fake_origin.as_frame())
-
- def test_rewriting_error(self):
- _, zero_div_lineno = tf_inspect.getsourcelines(zero_div)
- src_map = {
- errors.CodeLocation(
- file_path=__file__, line_number=zero_div_lineno + 1):
- None
- }
- with self.assertRaisesRegexp(tf_errors.InvalidArgumentError,
- 'Integer division by zero'):
- z = zero_div_caller()
- zero_div_caller.ag_source_map = src_map
+ sess.run(ops)
+
+ all_function_names = set()
+ for frame in cm.exception.custom_traceback:
+ _, _, function_name, _ = frame
+ all_function_names.add(function_name)
+ self.assertNotEqual('test_function_name', function_name)
+ self.assertIn('zero_div', all_function_names)
+
+ def test_improved_errors_failures(self):
+ loc, _ = self.fake_origin(zero_div, 2)
+ zero_div_caller.ag_source_map = {loc: 'bogus object'}
+
+ ops = zero_div_caller()
+ with self.assertRaises(tf_errors.InvalidArgumentError):
with errors.improved_errors(zero_div_caller):
with self.test_session() as sess:
- sess.run(z)
+ sess.run(ops)
- def test_no_ag_source_map(self):
+ def test_improved_errors_validation(self):
with self.assertRaisesRegexp(
ValueError,
'converted_function must be the result of an autograph.to_graph call'):
- with errors.improved_errors(None):
- pass
-
- def test_bad_ag_source_map(self):
+ errors.improved_errors(zero_div).__enter__()
with self.assertRaisesRegexp(
ValueError,
'converted_function must be the result of an autograph.to_graph call'):
- src_map = None
- zero_div_caller.ag_source_map = src_map
- with errors.improved_errors(None):
- pass
+ zero_div_caller.ag_source_map = 'not a dict'
+ errors.improved_errors(zero_div_caller).__enter__()
if __name__ == '__main__':
diff --git a/tensorflow/contrib/autograph/examples/integration_tests/BUILD b/tensorflow/contrib/autograph/examples/integration_tests/BUILD
index 2a4a0f75e7..6c281485b4 100644
--- a/tensorflow/contrib/autograph/examples/integration_tests/BUILD
+++ b/tensorflow/contrib/autograph/examples/integration_tests/BUILD
@@ -17,11 +17,25 @@ filegroup(
)
py_test(
+ name = "errors_test",
+ srcs = [
+ "errors_test.py",
+ ],
+ srcs_version = "PY2AND3",
+ tags = ["no_windows"],
+ visibility = ["//visibility:public"],
+ deps = [
+ "//tensorflow:tensorflow_py",
+ ],
+)
+
+py_test(
name = "keras_test",
srcs = [
"keras_test.py",
],
srcs_version = "PY2AND3",
+ tags = ["no_windows"],
deps = [
"//tensorflow:tensorflow_py",
],
@@ -33,6 +47,7 @@ py_test(
"list_literals_test.py",
],
srcs_version = "PY2AND3",
+ tags = ["no_windows"],
deps = [
"//tensorflow:tensorflow_py",
],
diff --git a/tensorflow/contrib/autograph/examples/integration_tests/errors_test.py b/tensorflow/contrib/autograph/examples/integration_tests/errors_test.py
new file mode 100644
index 0000000000..f4b9159942
--- /dev/null
+++ b/tensorflow/contrib/autograph/examples/integration_tests/errors_test.py
@@ -0,0 +1,162 @@
+# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Error traceback rewriting integration tests."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import tensorflow as tf
+
+from tensorflow.contrib import autograph as ag
+from tensorflow.python.util import tf_inspect
+
+
+class ErrorsTest(tf.test.TestCase):
+
+ def test_graph_construction_error_rewriting_call_tree(self):
+
+ def innermost(x):
+ if x > 0:
+ return tf.random_normal((2, 3), mean=0.0, dtype=tf.int32)
+ return tf.zeros((2, 3))
+
+ def inner_caller():
+ return innermost(1.0)
+
+ def caller():
+ return inner_caller()
+
+ with self.assertRaises(ag.GraphConstructionError) as error:
+ graph = ag.to_graph(caller)
+ graph()
+ expected = error.exception
+ custom_traceback = expected.custom_traceback
+ found_correct_filename = False
+ num_innermost_names = 0
+ num_inner_caller_names = 0
+ num_caller_names = 0
+ ag_output_filename = tf_inspect.getsourcefile(graph)
+ for frame in custom_traceback:
+ filename, _, fn_name, _ = frame
+ self.assertFalse('control_flow_ops.py' in filename)
+ self.assertFalse(ag_output_filename in filename)
+ found_correct_filename |= __file__ in filename
+ self.assertNotEqual('tf__test_fn', fn_name)
+ num_innermost_names += int('innermost' == fn_name)
+ self.assertNotEqual('tf__inner_caller', fn_name)
+ num_inner_caller_names += int('inner_caller' == fn_name)
+ self.assertNotEqual('tf__caller', fn_name)
+ num_caller_names += int('caller' == fn_name)
+ self.assertTrue(found_correct_filename)
+ self.assertEqual(num_innermost_names, 1)
+ self.assertEqual(num_inner_caller_names, 1)
+ self.assertEqual(num_caller_names, 1)
+
+ def test_graph_construction_error_rewriting_class(self):
+
+ class TestClass(object):
+
+ def test_fn(self):
+ return tf.random_normal((2, 3), mean=0.0, dtype=tf.int32)
+
+ def inner_caller(self):
+ return self.test_fn()
+
+ def caller(self):
+ return self.inner_caller()
+
+ # Note we expect a TypeError here because the traceback will not be
+ # rewritten for classes.
+ with self.assertRaises(TypeError):
+ graph = ag.to_graph(TestClass)
+ graph().caller()
+
+ def test_runtime_error_rewriting(self):
+
+ def g(x, s):
+ while tf.reduce_sum(x) > s:
+ x //= 0
+ return x
+
+ def test_fn(x):
+ return g(x, 10)
+
+ compiled_fn = ag.to_graph(test_fn)
+
+ with self.assertRaises(ag.TfRuntimeError) as error:
+ with self.test_session() as sess:
+ x = compiled_fn(tf.constant([4, 8]))
+ with ag.improved_errors(compiled_fn):
+ sess.run(x)
+ expected = error.exception
+ custom_traceback = expected.custom_traceback
+ found_correct_filename = False
+ num_test_fn_frames = 0
+ num_g_frames = 0
+ ag_output_filename = tf_inspect.getsourcefile(compiled_fn)
+ for frame in custom_traceback:
+ filename, _, fn_name, source_code = frame
+ self.assertFalse(ag_output_filename in filename)
+ self.assertFalse('control_flow_ops.py' in filename)
+ self.assertFalse('ag__.' in fn_name)
+ self.assertFalse('tf__g' in fn_name)
+ self.assertFalse('tf__test_fn' in fn_name)
+ found_correct_filename |= __file__ in filename
+ num_test_fn_frames += int('test_fn' == fn_name and
+ 'return g(x, 10)' in source_code)
+ # This makes sure that the code is correctly rewritten from "x_1 //= 0" to
+ # "x //= 0".
+ num_g_frames += int('g' == fn_name and 'x //= 0' in source_code)
+ self.assertTrue(found_correct_filename)
+ self.assertEqual(num_test_fn_frames, 1)
+ self.assertEqual(num_g_frames, 1)
+
+ def test_runtime_error_rewriting_nested(self):
+
+ def test_fn(x):
+
+ def g(y):
+ return y**2 // 0
+
+ s = 0
+ for xi in x:
+ s += g(xi)
+ return s
+
+ compiled_fn = ag.to_graph(test_fn)
+
+ # TODO(b/111408261): Nested functions currently do not rewrite correctly,
+ # when they do we should change this test to check for the same traceback
+ # properties as the other tests. This should throw a runtime error with a
+ # frame with "g" as the function name but because we don't yet add
+ # try/except blocks to inner functions the name is "tf__g".
+ with self.assertRaises(ag.TfRuntimeError) as error:
+ with self.test_session() as sess:
+ x = compiled_fn(tf.constant([4, 8]))
+ with ag.improved_errors(compiled_fn):
+ sess.run(x)
+ expected = error.exception
+ custom_traceback = expected.custom_traceback
+ num_tf_g_frames = 0
+ for frame in custom_traceback:
+ _, _, fn_name, _ = frame
+ self.assertNotEqual('g', fn_name)
+ num_tf_g_frames += int('tf__g' == fn_name)
+ self.assertEqual(num_tf_g_frames, 1)
+
+
+if __name__ == '__main__':
+ tf.test.main()
diff --git a/tensorflow/contrib/autograph/examples/notebooks/dev_summit_2018_demo.ipynb b/tensorflow/contrib/autograph/examples/notebooks/dev_summit_2018_demo.ipynb
index a3109fa5db..7e9cc54d4c 100644
--- a/tensorflow/contrib/autograph/examples/notebooks/dev_summit_2018_demo.ipynb
+++ b/tensorflow/contrib/autograph/examples/notebooks/dev_summit_2018_demo.ipynb
@@ -392,7 +392,7 @@
"output_type": "stream",
"text": [
"Got error message: assertion failed: [Do not pass zero!]\n",
- "\t [[Node: f/Assert/Assert = Assert[T=[DT_STRING], summarize=3, _device=\"/job:localhost/replica:0/task:0/device:CPU:0\"](f/NotEqual, f/Assert/Assert/data_0)]]\n"
+ "\t [[{{node f/Assert/Assert}} = Assert[T=[DT_STRING], summarize=3, _device=\"/job:localhost/replica:0/task:0/device:CPU:0\"](f/NotEqual, f/Assert/Assert/data_0)]]\n"
]
}
],
diff --git a/tensorflow/contrib/autograph/examples/notebooks/graph_vs_ag_vs_eager_sum_speed_test.ipynb b/tensorflow/contrib/autograph/examples/notebooks/graph_vs_ag_vs_eager_sum_speed_test.ipynb
new file mode 100644
index 0000000000..32742bec7e
--- /dev/null
+++ b/tensorflow/contrib/autograph/examples/notebooks/graph_vs_ag_vs_eager_sum_speed_test.ipynb
@@ -0,0 +1,519 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "moMkWaT_TTHi"
+ },
+ "source": [
+ "This Colab illustrates the differing overhead* between a custom, vectorized graph operation and a loop over a tensor\n",
+ "that computes the same function. The loop is implemented in TensorFlow Eager mode using Python syntax and control-flow, and using AutoGraph which takes a python function and converts it into graph mode. In AutoGraph the Python loop is converted into a tf.while_loop.\n",
+ "\n",
+ "The actual computation, summing a small number of scalar values, takes very little time to compute, so the graphs below are showing the overhead of the differing approaches. As such, this is more of a \"micro-benchmark\" than a representation of real-world performance of the three approaches.\n",
+ "\n",
+ "*Note the differing scales of the included plots"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "a0X_rfvuav98"
+ },
+ "source": [
+ "### Imports"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {
+ "autoexec": {
+ "startup": false,
+ "wait_interval": 0
+ }
+ },
+ "colab_type": "code",
+ "id": "EdxWv4Vn0ync"
+ },
+ "outputs": [],
+ "source": [
+ "!pip install -U -q tf-nightly"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {
+ "autoexec": {
+ "startup": false,
+ "wait_interval": 0
+ }
+ },
+ "colab_type": "code",
+ "id": "erq3_S7QsjkU"
+ },
+ "outputs": [],
+ "source": [
+ "from __future__ import absolute_import\n",
+ "from __future__ import division\n",
+ "from __future__ import print_function\n",
+ "\n",
+ "import numpy as np\n",
+ "import tensorflow as tf\n",
+ "import matplotlib.pyplot as plt\n",
+ "import math\n",
+ "import time\n",
+ "import random\n",
+ "from colabtools import adhoc_import\n",
+ "from tensorflow.contrib import autograph as ag\n",
+ "from tensorflow.python.framework import function"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "1JgnsXooa2RP"
+ },
+ "source": [
+ "### Testing boilerplate"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {
+ "autoexec": {
+ "startup": false,
+ "wait_interval": 0
+ }
+ },
+ "colab_type": "code",
+ "id": "UyD5LLjVZzny"
+ },
+ "outputs": [],
+ "source": [
+ "# Test-only parameters. Test checks successful completion not correctness. \n",
+ "burn_ins = 1\n",
+ "trials = 1\n",
+ "batches = 2\n",
+ "max_elements = 2"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "4_NBL0RQa8gY"
+ },
+ "source": [
+ "### Speed comparison parameters"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {
+ "autoexec": {
+ "startup": false,
+ "wait_interval": 0
+ }
+ },
+ "colab_type": "code",
+ "id": "Yq6daecyiJV5"
+ },
+ "outputs": [],
+ "source": [
+ "#@test {\"skip\": true} \n",
+ "burn_ins = 3 # Batches not counted in the average\n",
+ "trials = 10 # Batches run per vector-size (and averaged)\n",
+ "batches = 1000 # Number of random vectors summed over per trial\n",
+ "max_elements = 100 # Vectors of size 0 to this-1 will be executed and plotted"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "fiR8m13CbKH2"
+ },
+ "source": [
+ "### Random input"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {
+ "autoexec": {
+ "startup": false,
+ "wait_interval": 0
+ }
+ },
+ "colab_type": "code",
+ "id": "d8vrTlyNXuxc"
+ },
+ "outputs": [],
+ "source": [
+ "# Construct a random num x 1 tensor\n",
+ "def get_elements(num):\n",
+ " return tf.random_uniform(shape=(num, 1), maxval=1)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "ILJ6SbF3bXFQ"
+ },
+ "source": [
+ "## Graph mode"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {
+ "autoexec": {
+ "startup": false,
+ "wait_interval": 0
+ }
+ },
+ "colab_type": "code",
+ "id": "vovRf597X55n"
+ },
+ "outputs": [],
+ "source": [
+ "def tf_sum(elements):\n",
+ " # Using custom vectorized op\n",
+ " return tf.reduce_sum(elements)\n",
+ "\n",
+ "def run_trial(num):\n",
+ " elements = get_elements(num)\n",
+ " return tf_sum(elements)\n",
+ "\n",
+ "\n",
+ "\n",
+ "graph_means = []\n",
+ "for num in range(max_elements):\n",
+ " with tf.Graph().as_default():\n",
+ " durations = []\n",
+ " foo = run_trial(num)\n",
+ " \n",
+ " with tf.Session() as sess:\n",
+ " \n",
+ " for _ in range(burn_ins):\n",
+ " for _ in range(batches):\n",
+ " sess.run(foo)\n",
+ " \n",
+ " for _ in range(trials):\n",
+ " \n",
+ " start = time.time()\n",
+ " for _ in range(batches):\n",
+ " sess.run(foo)\n",
+ " \n",
+ " duration = time.time() - start\n",
+ " durations.append(duration) \n",
+ " \n",
+ " graph_means.append(np.mean(durations)) "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {
+ "autoexec": {
+ "startup": false,
+ "wait_interval": 0
+ },
+ "height": 301
+ },
+ "colab_type": "code",
+ "executionInfo": {
+ "elapsed": 278,
+ "status": "ok",
+ "timestamp": 1532447361278,
+ "user": {
+ "displayName": "",
+ "photoUrl": "",
+ "userId": ""
+ },
+ "user_tz": 240
+ },
+ "id": "Jm9Blkyx90Eq",
+ "outputId": "d83cd51f-7e56-4d73-f7df-bb157dee46df"
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAa8AAAEcCAYAAABwNTvaAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3WdgVFXegPFnZtI7kJ5QQwlCKIGERGroEoSAYZFVEFAR\ngV1XXHvbFWEtK6xlUVgRXMuLiqBSZQUh9E5ChxRIn/ReJjNz3g8hNxkzCUMJkHh+X2DmtnPPnXv+\n95R7ohJCCCRJkiSpGVHf6QRIkiRJ0vWSwUuSJElqdmTwkiRJkpodGbwkSZKkZkcGL0mSJKnZkcFL\nkiRJanaaJHitWLGCV199tSl23WINHz6cAwcONPlxXnzxRd5///0mP87dIikpiUmTJtGvXz++/PLL\nO52cFi0+Pp4HHnjgTifjmsaPH8+RI0du6T5/b/eVObcqX9966y3Wrl17zfWsbmTnffv2RaVSAVBe\nXo6NjQ1qtRqVSsUbb7zBE088cSO7vW5paWmMGDGCs2fPolY3n0rkiy++iLe3N0899dSdTspdoSmv\n46effsqAAQPYsGGD2eVbt27l888/5/z58/Tq1Yv//ve/JsvPnTvHyy+/TGJiIgEBASxevJjAwEBl\n+bvvvsu6detQqVQ88MADPPvssxZv29SmT5/OxIkTiY6Ovi3H++CDD3jsscea9BjDhw9n8eLFhIeH\n3/A+Nm3adAtT1LgVK1bwySefoFKp0Ov16PV67OzsEELg7+/Pxo0bCQwMxN7eHpVKhRACa2trDh8+\nfNvSeCPMlWG3Kl8fffRRpkyZQnR0NFZWDYeoGyopTpw4wfHjxzl+/Di+vr6sWLFC+W78+PE3nOjr\nJYRQLrjUfDXldUxPT6dz584NLndzc2PmzJnMmTOn3rKqqirmz59PVFQUR44cISoqinnz5qHX6wFY\nu3YtO3fuZOPGjfz000/s2rWLb775xqJtm4PruR7Z2dkcOnSIESNGNGGKbo7BYLjtx3ziiSeUsvHv\nf/87ffv25fjx45w4cYKNGzcCoFKp+Omnn5Tv73TguhP5VJeHhwcBAQHs3Lmz0fVu+jFXCFHvR/7R\nRx8pT6BpaWkEBgayfv16hg0bxoABA1i7di2nTp1iwoQJhIaGsmjRIpPt161bx7hx4xgwYACPPfYY\n6enpZo89ffp0APr3709wcDCxsbEIIVi+fDnDhw9n4MCBvPDCC5SUlJjdPj8/n7lz5xISEsKAAQN4\n+OGHlWWBgYGkpKQon+s2Cxw+fJihQ4fy6aefcu+99zJ48GB++eUXdu/ezZgxYxgwYAArVqwwe8xv\nv/2WjRs38umnnxIcHMyTTz6pLDt37hwTJkwgJCSEhQsXotPplGW//vorUVFRhISEMG3aNC5cuGB2\n/wAJCQnMnj2bAQMGcN9997F169YG121sv8OHD2fVqlVMmDCBvn378sorr5Cbm8vjjz9OcHAws2fP\npri4WFn/5MmTPPjgg4SEhBAVFWVyE06fPp3333+fadOmERwczKOPPkpBQYGyDEyvY3JyMtOnT6d/\n//6Eh4ezcOHCBs9hx44djB8/ntDQUGbMmEFiYiIAjzzyCIcOHeKNN94gODiYK1eu1Ns2PDycsWPH\n4uHhUW/Z4cOHMRgMzJgxA2tra6ZPn44QgoMHDwLwww8/MHv2bDw9PfH09GTWrFlKDe/QoUONblvX\nli1b6jW3rVmzhnnz5gGg0+l4++23iYiIYNCgQfztb38z+W388ssvREVF0a9fP0aPHs3evXtZtmwZ\nx44dY9GiRQQHB/Pmm28CcPz4caKjowkJCWHKlCmcOHHC5BotW7aMadOm0adPH1JTU1m/fj0jR44k\nODiYkSNHNvh0vW/fPnr06IGNjQ0AK1eu5M9//rPJOm+++SaLFy8GoKSkhJdffplBgwYxdOhQ/vWv\nf5mUI99++y3jxo0jODiY8ePHc+7cOZ577jkyMjJ48sknCQ4OZtWqVUD965+QkKDsZ/jw4fznP/9R\nfsMGg8GkiT4kJITg4GCCg4Pp27cvgYGBSnnT2L1x9uxZJk+eTL9+/Xj66aeprKw0my+WsPQhobGy\nraac/fbbbxk8eDCDBw9m9erVJtuuXLmSUaNGERYWxtNPP01RUZHJtuvWrSMiIoKZM2cC8NRTTzFo\n0CBCQkKYPn26kq8NlWF181Wn07F48WIGDx7MkCFDWLJkCVVVVUBt+bl69Wql/Fy/fr3JuYaEhLBr\n165rZshNiYiIEPv37zf57sMPPxTPPvusEEKI1NRU0a1bN/H666+LyspKsW/fPhEUFCTmz58v8vLy\nRGZmpggPDxdHjhwRQgjxv//9T4wePVokJiYKg8EgPv74YzF16lSzx05NTRWBgYHCaDQq33333Xdi\n9OjRIjU1VZSVlYkFCxYoafmt9957T7z++uvCYDAIvV4vjh49qiwLDAwUycnJyucXXnhB/Otf/xJC\nCHHo0CFxzz33iOXLlwu9Xi++/fZbERYWJp555hlRVlYmLl26JIKCgkRKSorZ49bdV918nDJlisjO\nzhaFhYXivvvuE2vXrhVCCHH69GkRHh4u4uLihNFoFBs2bBARERFCp9PV23dZWZkYOnSo2LBhgzAa\njeLs2bNiwIABIj4+vt6xr7XfiIgIMXXqVJGbmyu0Wq0IDw8XkyZNEufOnRM6nU7MmDFDfPTRR0II\nITIzM0VoaKiIiYkRQgixf/9+ERoaKvLy8oQQQjz88MNi1KhR4sqVK6KyslI8/PDD4r333mvwOi5c\nuFB88sknQgghKisrxbFjx8zmZWJioujTp4/Yv3+/0Ov14j//+Y8YNWqUqKqqUo773Xffmd22rm+/\n/VZMnz7d5LvVq1eLxx9/3OS7J554QqxevVoIIUS/fv1EbGyssuzUqVMiODjYom3rKi8vF8HBweLK\nlSvKdw888IDYsmWLEEKIN998Uzz55JOiqKhIlJaWirlz54qlS5cKIYSIjY0V/fr1U+5BrVYrEhMT\nzZ57QUGBCAkJET/99JMwGAxi06ZNIiQkRBQUFCjrR0REiPj4eGEwGERxcbEIDg4Wly9fFkIIkZ2d\nrfyOfuvtt98Wb7zxhvI5LS1N9OnTR5SUlAghhDAYDGLgwIFKfj355JPi9ddfFxUVFSI3N1dMmTJF\nfPPNN0IIIbZs2SKGDBkiTp8+LYQQIjk5WaSnpwshqn+TBw4cUI5zresfEREhoqKiRGZmpqisrFS+\n+22ZJYQQS5cuFQ8//LDQ6/WN3hs6nU5ERESIzz//XOj1erFt2zbRo0ePevf0b61fv1788Y9/rPd9\nt27dTMqahjRWttWUswsXLhQVFRXiwoULIiwsTDnP1atXi6lTpwqtVit0Op147bXXxMKFC022ff75\n50V5ebmST99//70oKysTOp1OLFmyREycOFFJS0NlWM3x/vWvf4mpU6eKvLw8kZeXJ6ZOnSref/99\nIURt+fnhhx8KvV4vdu3aJXr37i2KioqUfW3fvl1MmjSp0fy4LR1FKpWK+fPnY2Njw7333ou9vT2R\nkZG0atUKLy8v+vfvz9mzZwH45ptvmDNnDh07dkStVjNnzhzOnz9PRkZGYwFY+f+mTZuYOXMmfn5+\n2Nvbs3DhQrZs2YLRaKy3nZWVFdnZ2aSmpqLRaOjXr5/ZfZpjbW3N3Llz0Wg0jBs3jvz8fB555BHs\n7e3p3LkznTt3brR2ZM6MGTNwd3fHxcWFiIgIzp07B8B3333Hgw8+SFBQECqViqioKGxsbIiNja23\nj19//RV/f3+ioqJQqVR0796d0aNHs23btnrrWrLfhx9+mNatW+Pp6Un//v3p3bs3gYGBWFtbM2rU\nKCWNP/30E8OGDWPw4MFAdY2mZ8+e7N69W9nX5MmTadeuHTY2Ntx3333KtjXq5rmVlRVpaWlotVps\nbGwIDg42m2dbt25l2LBhhIeHo9FoePTRR6moqDCpUdyosrIynJ2dTb5zcnJSnnZ/u9zZ2ZmysjKL\ntq3Lzs6OESNGKLWay5cvk5SUpDTBrVu3jhdffBFnZ2ccHByYM2eOsu66deuIjo5W+oA8PT3p2LGj\n2fPZtWsXHTp04P7770etVhMZGUmnTp349ddflXUmTZpEQEAAarUajUaDRqPh4sWLVFZW4u7uTkBA\ngNl9FxcX4+joqHz29fXlnnvu4ZdffgHgwIEDODg40KtXL3JyctizZw8vvfQStra2tG7dmkceeYTN\nmzcr5/TYY4/Ro0cPANq2bYuPj4+y77q/E0uu/4wZM/Dy8lJqheZs2bKFTZs28eGHH6LRaBq9N2Jj\nY9Hr9cyYMQONRsOYMWPo2bNng/u2xKRJkwgJCSE0NFSpnf6WJWXbn/70J2xtbenatSuTJ09W8vTb\nb7/lL3/5C56enlhbWzN//nx+/vlnZVuVSsWf/vQn7OzslHyaPHky9vb2yvrnz59vsBXLXFrnz59P\nq1ataNWqFQsWLODHH39UlltbWzNv3jw0Gg1Dhw7FwcGBpKQkZbmjo6NJq445NzRg40a0adNG+b+d\nnR3u7u7KZ1tbW+WmT09PZ/Hixbz99ttAbX+IVqs1+QE3JCsrC19fX+Wzn58fer2enJwcPD09TdZ9\n7LHH+PDDD5k9ezYqlYopU6aY7fswx83NTRm0YmdnZ/Yca87JUnW3t7e3Jzs7G6jOkx9//FEZLSeE\nQK/Xk5WVVW8f6enpnDx5ktDQUGVdg8FAVFSU2XWvtd+6abK1ta33ue5127p1q1IQ1uyrbsd63Wtu\nb2/faP4899xz/Otf/yI6OlrplzI3ku2311ulUuHj44NWq21w35ZycHCod7OWlJTg5ORkdnlJSQkO\nDg4WbftbkZGRvPPOO8ybN49NmzYxcuRIbGxsyMvLo7y83OTcjUajUoBnZmYydOhQi87nt3kF1UGm\nbl55e3sr/7e3t2fZsmWsWrWKl156iX79+vHcc8/RqVOnevt2cXGhtLS03jlt3ryZiRMnsmnTJqU/\nPD09Hb1ez6BBg4Daroea+zszM5N27drd0DmZu/51z8mcs2fPsmjRIlavXo2bm5uSxsbuDS8vL5N9\n+Pn5WZTehmzYsIG2bds2uk5jZRtUn3vdc/X19eXSpUvK+SxYsEAZECWEwMrKStkWTPPJaDSydOlS\nfv75Z/Lz81GpVKhUKvLz8xv8DTeWVl9fX5Nyxc3NzWRwlp2dncnvp7S0tN7D32/dtuBlKW9vb558\n8kmLBn7UBI+6PD09TfrI0tLSsLKyMik4azg4OPD888/z/PPPk5CQwPTp0+nVqxdhYWHY29tTXl6u\nrJudnX3Nm6CpeHt7M3fuXItGcfr4+DBgwAClP+BW7deS40ZFRfHGG29c97bmrmObNm2UvtBjx44x\na9YsQkND693gnp6eyg1aIyMj45Zcqy5durBmzRqT7y5evKj00XXu3Jnz588TFBQEVPdZdunSpdFt\n6/ar1jVo0CBefPFFzp8/z+bNm3nppZcAaNWqFfb29mzatKnewxdUX8O6fbN1/TZfPT092b59u8l3\n6enpDBkypMFtBg4cyMCBA9HpdCxbtoxXX32Vr776qt6xunXrZvJkDTB27FjeeecdtFotv/zyizKY\nxcfHB1tbWw4dOmT22nt7e5OcnGzxOd3M9c/Ly2PBggW8/vrrJiNBG7s3jhw5Uu/hKD093eKAe6Ma\nK9syMjIQQpCRkaHUvDMyMpTfjI+PD0uWLKFv37719puWlgaY5u3GjRv59ddf+fzzz/H19aW4uJiQ\nkJDrSmtaWppSU09PTzf7+21IQkLCNUfm3pZmw2s1wdU1bdo0VqxYQXx8PFDdHGGuyQugdevWqNVq\nkx96ZGQka9asITU1ldLSUpYtW0ZkZKTZIdi7du1StnVwcFCaSaB6wMamTZswGo3ExMTc0vdC3N3d\nGyxwzPnDH/7A2rVriYuLA6qbpHbv3m225jJs2DCSkpL48ccf0ev1VFVVcerUKWUQw43u91omTJjA\nzp072bt3L0ajkcrKSg4fPmxRDcjcddy2bZuyrYuLC2q12uw1vO+++9i1axcHDx5Er9ezatUqbG1t\n6dOnj0XpNhqN6HQ69Hq9yf8BQkNDUavVfPHFF+h0OuUpfMCAAQBERUWxZs0atFotWq2WNWvWMHny\n5Ea3DQsLM5uOmuand955h6KiIgYOHAigtAgsWbKEvLw8ALRaLXv37gUgOjqa9evXc/DgQYQQaLVa\n5Vr/9nc2dOhQrly5wubNmzEYDGzZsoXExEQiIiLMpik3N5edO3dSXl6OlZWVco+YM3DgQM6cOWMy\nkKR169aEhITw4osv0rZtW6XG5uHhwcCBA1myZAklJSUIIUhJSVHusSlTpvDZZ59x5swZAJKTk5Vu\nA3d3d1JTU5Vj3Mz1NxgM/OlPf2LChAmMHTvWZFlj90afPn2wsrLiiy++wGAwsH37dk6dOnXN490s\nS8q25cuXU1FRwaVLl1i/fj2RkZEATJ06laVLlyrBLy8vjx07dijb/baMLi0txcbGBhcXF8rKynjv\nvfdMgtu1yrDIyEg+/vhj8vLyyMvLY/ny5UycONHicz1y5IjJQ5U5Nx28zD05XWudxj6PHDmSxx9/\nnKeffpr+/fszYcIE9uzZY3a/dnZ2zJ07l2nTphEaGkpcXBzR0dFMnDiRhx9+mFGjRmFvb88rr7xi\ndvvLly8zc+ZM+vbty7Rp03jooYeUp4uXX36ZnTt3EhISwubNmxk5cuRNnWNd0dHRxMfHExoayoIF\nC665fs+ePVm0aBFvvPEGoaGhjBkzpsH3lhwdHfnss8/YsmWLMurovffeMylULN3v9ZyTt7c3y5cv\nZ8WKFYSHhxMREcFnn32m3BSNbWvuOp46dYopU6YQHBzM/Pnzefnll802zXTs2JF3332XRYsWER4e\nzq5du/jkk0+U90Ou9fv88ccf6dWrF2+88QbHjh2jd+/eygv21tbWLF++nA0bNhAaGsr69etZvny5\nsu8HH3yQiIgIJkyYwIQJE4iIiOAPf/iDRduaExkZyYEDB7jvvvtMCqS//vWvtG/fnj/84Q/079+f\n2bNnc/nyZQB69erFkiVLWLJkCf369WPGjBlKQT9jxgy2bdvGgAEDWLx4MW5ubnzyySesWrWKsLAw\nVq1axYoVK3B1dTWbV0ajkdWrVzNkyBDCwsI4cuQIr7/+utm0t2nThrCwMKWPq8b48eM5cOAA999/\nv8n3b7/9NlVVVURGRhIaGspTTz2lNJOPHTuWuXPn8swzzyjXv7CwEIA5c+awfPlyQkNDWb169Q1d\n/5rvMjMzOX78OJ9//rky2jA4OJjMzMxG7w1ra2s+/PBD1q9fT2hoKNu2bWP06NENXtdrsaQMBSwq\n20JDQxk1ahSzZs3iscceU5rtH3nkEUaMGMHs2bPp168fDz74oBKYzaUhKioKHx8fhgwZwvjx4+vV\n2K5Vhs2bN4+ePXsyYcIEJk6cSM+ePZk7d65FeZCVlUVCQsK1y1xxPdUiSZKkBiQkJPDCCy/w3Xff\n3emk/O6kpaUxcuRIzpw506wmbDDn7bffpl27dkybNq3R9WTwkiRJauaa62xDN+P3cZaSJEktnKXN\njy2FrHlJkiRJzY6seUmSJEnNzl33ntfN0OsN5Odf/zDvlqhVKweZF1fJvKgl86KWzItaHh6NvxB8\nN2pRNS8rK/PvoPweybyoJfOilsyLWjIvmrcWFbwkSZKk3wcZvCSpBSsq1VFcVv8FdUlq7mTwkqQW\n7J9rT/LBurhrryhJzUyLGrAhSVIto1GQnlOKrY3s25FaHlnzkqQWqri8CqMQlFfq0Rvq/z07SWrO\nZPCSpBaqsKT2T9MXl1XdwZRI0q0ng5cktVAFJbUDNeSgDamlkcFLklqoujWvIhm8pBamyYNXTEwM\nY8eOZcyYMaxcubLe8qNHjzJ58mR69OhR76+8QvWfTh8yZAhvvvlmUydVklqUgtI6Na9S2WwotSxN\nGryMRiOLFi1i1apVbNq0ic2bN5OQkGCyjq+vL2+99Va9P1ZX4/333yc0NLQpkylJLZJpn5eseUkt\nS5MGr7i4ONq3b4+fnx/W1tZERkaa/OlpqA5eXbt2NTud/+nTp8nLy2PQoEFNmUxJapEK6/R5FckB\nG1IL06TBS6vV4uPjo3z28vIiKyvLom2FELz99ts899xzyL/aIknXr6BU1ryklqtJX1K+maDz9ddf\nM2zYMLy8vK5rX81xduSmIvOi1u8xL4rL9djbaiivNFCpF0oe/B7zoiEyL5qvJg1e3t7epKenK5+1\nWi2enp4WbXvixAmOHz/O119/TWlpKXq9HkdHRxYuXNjodtnZxTeV5pbCw8NZ5sVVv8e8EEKQV1iB\nn4cjqVkl5BaUkZ1d/LvMi4bIvKjVHIN4kwavoKAgkpOTSUtLw8PDg82bN7N06dIG169bu/rnP/+p\n/H/Dhg2cOXPmmoFLkqRqZVdn1WjlZEthSaUcKi+1OE3a56XRaHj11VeZPXs248ePJzIykoCAAD74\n4AN+/fVXAE6dOsXQoUPZtm0br7/+eoOjDiVJslxBcXV/l6uTDS4ONnLAhtTiqEQLGw0hmwGqySaR\nWr/HvDhzOY/31p5kwsAOJKQVcuZyPp88MxQ/X7ffXV405Pf4u2hIc2w2lDNsSFILVPOOl5uTLc6O\nNoCc31BqWWTwkqQWqOYdL1cnG5ztrwavctnvJbUcMnjdJpU6A2u2nufoecvec5Okm1EzKa+bky0u\njtYAFMkpoqQWRAav2+RkfA4xseks/+E0KzeeobRCFiRS0ym8+oKyq6MNzg41zYay5iW1HPIvKd8m\nlzOLAGjjYsfBM1ouJBfwxIQedG3rdodTdnuUlFdx8lIO+cUV5JfoqKjUM3FQR7xaO9zppLVIBSU6\nVICLow3ODtU1L9nnJbUkMnjdJpczilEBf58dwi9HU/lp32VWbz3PP+aE3emk3RZfbr/A4XOmTaat\nXGyZMqzzHUpRy1ZYUomzgzVWGjUuV2te8l0vqSWRzYa3gVEILmuL8XF3xMHOmgmDOtLZz4WsvDKq\n9IY7nbwmpzcYiUvIpY2LLU//oTcvPhwMQFp26R1OWctVUKrD1ckWoE7Nq+UEr5LyKjnn6e+cDF63\ngTavjEqdgQ7ete9SeLdxQADa/PI7l7Db5GJKARU6A327eBDUqQ1d/N1wdbIhNbvkTietRarQ6anU\nGXB1qq5x1fZ5tYxmw0upBfz5/T38c+1Jsgta/v0jmSeD121wObP6RUiT4NXaEYDM3LI7kqbbKTY+\nF4Dend2V7/w9nMgrqqRMDly55WqGybs5Vte87Gw0WGnULabmde5KvvLvq6sO8cvRFIyyFva7I4PX\nbXA5oyZ4uSjfeV8dqJCZ1/KDV1xCDrY2GpPBKf4e1cE7VTYd3nIFJbVTQwGoVCpcHK1bzFD5mt/M\nlIgArDVqvv7lEv/dduEOp0q63eSAjdvgcmYRKhW09XJSvvNu8/sIXpl5ZWjzy+nX1QNrq9pnJX+P\n6rxIyy753Yy4vF56g5HSCj2uV2fIsFRhae07XjWc7W3IyL27HxTKKqrYeiiZvKJKist1lJRVMaS3\nL8P6+pmsl5Zdgr2tFWND23FvTx+WfHGUg2cyeWhUF6ytNHco9dLtJmteTcxoFFzRFuPn7oitde2N\n5e5qh0atQtvCg1dsfA4AvTq3Mfm+JnjJmlfDvvrfRZ7/ZD9FpdfX3KdMylsn6Dk7WqPTG6mo1N/S\nNN5K2w4ns/nAFQ6cyeR0Yh6XM4v5+UiKyTpVegPavHL8PBxRqVS4OtrQr6snOr2R88kFdyjl0p0g\ng1cTy8gtRVdlNGkyBLDSqPFwsyczr6xFj5pSgleAu8n3Pm0cUKmQgzYaUFpRxf7TmeiqjCSmF13X\ntgVmal41w+VrmhTvNlV6I7tPpuNoZ8U/ngjj44VD6d6+Fdq8MpMX+tNzyjAKQVuP2laMoIDqB6O4\nq32rUjVtfhknLmbf6WQ0GRm8mpgyWMOn/qzN3q0dKK3QU1x+Y30Rd3vQK6uo4lJqIR19XOo1fdlY\na/Bq5UBqduldfx51ZeSWsnzDKfKLmzYIHDidSZXeCNS+4G6pwt/0eUHtcPnrrcXdLkfPZ1FcVsXg\n3r54tXLA1kZDJ9/qB76kOsG75mGnps8UoIu/K/a2GmITcprVb6mprd5yng/XnyIhvfBOJ6VJyODV\nxMwN1qihDNpoZMThtzvj+b9fLtX7Pq+ogqc+2Mv2w8m3KKW33umkPAxGQe+ANmaX+3s4Ul6pNwkE\nCWmFrN1xCYPReLuSeV02xCRy9EI2/zuaUm9ZUZmO9JybbwYVQhATm45GrQJqH4AsVTuvYW3wuttr\nXjuOp6ICIur0b9UEr8SM+sHLr07Ny0qjpkeH1uQUVpDxOxi9a4n84koupVQ3o/58uP5vtSWQwauJ\nXc4sQqNW0dbTsd6yaw3aiE8tZNvhZP53NKVeoRgTm05JeRUHz2pvfaJvQn5xJTmF5RSWVHL8apNF\n3SHyddX2e9U2Ha7deYntR1K4eBf2X+QUlnPs6jntP52J3lAbYIUQfPh9HH9bfZicm3z3KDG9iNTs\nUvp29aCNiy2XM4quq0ZRWKrD0c7KZPCCU03N6y4MXkkZRSSmF9G7szsebvbK9518XQFMmk1r+kjr\n1rygtlk6LkE2HQIcOZ+FADRqFccuZLXI9+Fk8GpCeoOR5KwS/DwczY6Camy4vBCC73cnKJ93nUxT\n/m8wGtkTlwHAFW3xLZvk1yjETTW7nLyUwzP/3sdzHx/g6Y/2cfhcFm5ONrSrM8qyLj9lxGF1gZSZ\nV0ZCWnVBdSn17mvq2Hk8DSGqB9sUleo4lVhbUF5MKSAhrQi9QbD54JWbOs7u2HQAhvb2pYO3C0Vl\nVdfVTFlYUqnMrlGjtuZ19zUb7jyWCsDwfqajCl0dbWjjYkdiem3wTs0uoY2LLQ521ibrKv1eCTm3\nIcV3vyPntKhUED0sACEw21LQ3Mng1YTSc0qp0tcfrFGjsWbDM5fzuJBSQI+OrXF1tGH/qUwqq6qn\nkjqVmEd+cSU21mqE4JbUUsor9Ty7fD///fnG3pcxCsH6mERUKgjr4UVIoCd9u7jz4IguqFQqs9v4\ne9a861WD0HifAAAgAElEQVRd89p/OlNZdjH17qp5VeoMxJxMx8XBmicm9gBg79UHCIBth6qbb53s\nrdkbl0FuYcUNHae8Us/hc1rcXe3o3qGV0ldqadNhld5gdnh9zSwbhXdZzauoTMehc1l4tXbgng6t\n6y3v5OtCSXkV2YUVFJfpKCzRmTQZ1nB1tKGDtzOXUgspq7gzIypv9kXp3zah36icgnIS0osIbNeK\nEf38aeVsy57YjBb3lyxk8GpCjQ3WgOpOdAdbq3o1r+paVyIAU4YFMLi3L2VXCzWAmJPVT+YPDAkA\n4Fxy/k2n9XRSdUDcfTKds5fzrnv7ExdzSM0uYcA9Xsy5vwdPRvXkTw/0IrS7V4PbeLjZY2OtJjW7\nFKMQHDidgZ2NBk83exLSiu6qfq/9pzMoq9QzrK8fAb6utPNyIjY+l8KSStJySolNyKWznysPjuiM\nwXjjta9DZ7XoqowM7u2LWqVSHnwsHbRRaKa/C8DlarPh3RS8Siuq+GFPEnqDkeHBfqjNPOQo/V7p\nhXWaDM3X5HsFtMFgFDf0+71Ze2LTmbd0NxdTbuyhq7xSz5v/Pcqrnx666Vlnjlz9m4ED7vHCSqNm\nZH9/KqsM7L5abrQUMnjdpLKKKtbuuGS2Tfn81WlsOjZQ81KpVHi3cSC7oNykoD52IZsrmcWEdvek\nnZczQ3v7olLBrhPp5BVVEJuQQwdvZ4b19cPaSq0c52bUHVL7xc8XrmvCYCEEG/cloQLGh3eweDu1\nSoWfuyMZuaWcu5xPblEl/QM96d6hFZVVBpK1t3YYfUZuKa+tOsSWRgJLld5ITGw6b311nP/+fIH8\n4kqMQvC/o6lo1CplQMHgXr7VAfeMlp+v1rrGDmjHgHu88Gxlz57Y6mt1PfQGI7+eSEOtUjEoyAeA\n9lenFKsZ+NMQo1EQl5DD59vOA9RrNqyted1cs2FZhf6mA2BiehErN55h4Uf72HUiDRdHGwb29DG7\nbm3wKjI70rCumr7V2NvcdGgUgi0Hr6CrMvLpprOUX+e7dEIIVm0+R0ZuGWWVemJiM669USMOn8tC\no1YR3NUDgKG9/bCz0fDL0RQycku5kJzP0fNZN9w6cLeQM2zcpB/2JvHL0VS0eWU8NaW38n1uYQVH\nzmfh08bBZGaN3/Ju7UBiehE5BRV4tXbAaBRs2JOIWqUianAnANq42tGrUxtiE3L5+pdLCAFD+/hi\nbaWmi78rZy/nU1Sqw+U6Z2KooTcYiU3IpY2LHX27uvPL0VS2HExm4qCOFm1/Mj6H5KwSQrt74utu\nvmBpiJ+HE0kZxayPqa5pDuzpTV5RdQ3wUkoBHX3MB/7rVVSqY9m3seQUVrBuVwKujjYMDKotMCt0\nenYcS+WXo6nKDBUXUwrYfyqD3p3dycwr496e3kpQGHCPF9/svMTO46nkF1fi1dqBPl3cUatUjA/v\nwGdbzrH54BWmj+5mUfqMQvDZ5nOkZFXXXls5Vx/Hyd4ad1c7LmcWI4Qw2wR75nIen289T87VwqiT\nr4vJuQHY2miwsVYrf6SyMWcu53HyYg4TB3fEyb62b6msooq/rT5CaYWev88Kwb3O4ApLZeWX8Y8v\nj2EwCrxa2TOkty/3BvngYGe+KGrv5YxGrSIpvQjd1Wbzhmpe7b2dcXG0ITY+l437krC11mBtrUGv\nN1JRZUBXZSCwfSt6mGmevBnnLuejzS/H0c6KnMIKvv01nkfGBlq8/ZaDVzh+MZvO/q6kaEv45VgK\no0L80ajN1y1yCsrJK640OzONNq+MK9pigjq1Ua6dg50VQ3r7sv1ICi//55Cybr+uHsyfHHSdZ3v3\naPKaV0xMDGPHjmXMmDGsXLmy3vKjR48yefJkevTowfbt25Xvz58/z4MPPsj999/PxIkT2bJlS1Mn\n9bplFZTz6/HqgRSxCbkmo6K2H0nBYBTcN6C92eaQGjX9XhlXmw73nsogI7eMgUHeyjJAmSLn+MVs\nbG00SnNc9/atADh/E02H55PzKa/U07erO5MGd6KVsy2bD1y2aOoqIQQ/7b2MCrh/oGXBri7/q8Eu\nKaMId1c7urR1o0vb6lFmF2/RoI0KnZ7318WRU1jB0D6+ONhasWbreaWJJz6tkL99doTvdydSWWVg\nbGg73pkbzsz7AnG0t1aaYUb1b6vs08nemr5dPMgprMBgFIwJbatc5/CeXni42bEnNp2kjPrNffFp\nhRw4k6k0Dwkh+HL7RQ6e1RLg58LM3xR8HXyq+33MPSnnFJTz8YbTFJRUMqS3D6/PDOGVGf3xM/MQ\n4eJgQ2EjfSpVeiPf7LzEe2tPsuN4Kh//cFoZUSmE4L8/XyCnsILySj2fbjqL0Xj9fTw198VDo7qy\nZE4Y94W1b3T6KxtrDf4eTlzRlnA5sxiNWqWM0v0ttUpFv64elJRXsWFPEmt3xvPFzxf4vx2X2BCT\nyOYDV1i+4dRN9f2cuZxXr1lv5/HqASd/eqAX/h5O7D6ZbvGoxzNJeayPSaSVsy0LJgUxMKj64e3Y\nBfMvF5+8lMNrnx3mra+Om32lpKZrIbS7p8n348LaE9bDi0G9fIgMb88fR3Zh6vDm/bf0mrTmZTQa\nWbRoEWvWrMHT05Po6GhGjBhBQECAso6vry9vvfUWn332mcm29vb2vPPOO7Rr146srCwmT57MkCFD\ncHJquBZzu22IScRgFAzr68euE2n8uDeJp//Qm5LyKmJi02nlbEtYj4b7fMB00EZlOwM/7EnExkqt\n1LpqBHVqQxsXO3KLKhjQ3Qt72+pLF6gEr4JG+5cac+JidTNLcBcP7G2tmDaiC8t/OM0XP1/gmQf7\nNBp8YxNyuaItJiTQ02yBeS1+nrXX896e3qhVKtxd7WntYsul1AKT2sbxi9moVSr6dDE/9N4cg9HI\ne18dIymjiHt7ejNjTDdCAz1Z+m0sH60/xb09vatHYgkYG9qO8fd2UGoBQ9zsCbvHi10n0hDUNuHV\nGNzLhyPns3BxsGZgT2/le41aTdSgTvxn01kWfX6UPp3dGX9vB/KLK9l2+IoyotLaSk2/bh7YWGmI\niU2nracTT0/pja2N6cjUjt7OHD2fxeXMYpPajt5g5OMfz1BWqWfWuEAG9/JtNC+cHayVl8Lr1uCM\nRkFSRhFf/HyB5KwSvFrZ08bVjrOX8/lmZzwPjerKgTOZHD6XRYCfC26Othy7mM22w8mMC2tv8bUo\nLtOxNy6DNi62DO3j2+BAnt/q5OvCFW0xydoS/D0csdI0/Mz94IguhPf0plJnoPJqbcvaSo2tjYaz\nl/PZdiiZnw+nMHlI7f1VXqlnQ0wirVxs6dGhNf6eTmZ/86cTc1n6bSyd/Vx57o99sdKoyS2s4GR8\nDu29neni78rj99/DG2uOsHrrOSYO6kh8aiHxqYU4OVgzc2wg/nV+76cTc1nx0xk0ahXzJvXExdGG\nUf3b8uvxNLYfSTG5n4UQbD5whQ0xiVhZqfFws2P7kRTSckqZO7EH1ho1567ks/dUBlYaFX27eJik\n3cXRhjn397Aov5uLJg1ecXFxtG/fHj+/6lpDZGQkO3bsqBe8gHo/5Pbta28KT09P2rRpQ15eXqPB\nq6lmD9AbjPxn41kc7ayYEtEZe1srkjKKOHRWS3tvZx4e3ZXM3FJOJeYSn1bI2aQ8KqsMRA3u2OiN\nBqbD5bcfTaGgRMf4e9srzUY11GoVYwe045ud8QwPrh1S3MHbGTsbjfJnIqC6U/7AuSw6eTnh1dr8\nU2oNoxCcuJSNk721UuPp182D3gHVzZQ/H07mvgHmC6hKnYFvdsYDcP/ADo0epyF1m4DC6wSALv5u\nHDqrJTOvDJ82jqTnlLJ8w2mMQjDzvkCG9DZfUFfqDOyJS+dCcgEZeWVk5ZehNwgC27kx875AVCoV\n3Tu0ZvqYbqzZep7tR1Jo42LHY+O7061dq3r7s7HWMDq0ndlj3dOhNSOC/Qls71bvVYjwnt44O1jz\n077LnIzP4WR8bT9Mn87udPB25sCZTA6eqX5S9mrtwDNT+9QbAg61f0onKbOI/oG1T9TrdiWQlFFE\neA8vpY+sMc4ONlTpiymr1KPNK+dMUi4XUwtJSCukQlfdJDe4lw/TRnZBCFjyxTF2HEvF0c6K7UdS\nsLPR8Pj9PXCwtSI+vZANMYn06NC6XlBvyK8n0tDpjYwKaXfN+6KuTr4u/HqiuoWjoSbDGtZWajr7\nuZpd1sXfjf2nM/nlaAqjQ9pSU7x/uf0iB85Uj3T9jgScHawZHdKWyN/032692rcZn1bI97sTmDq8\nC7tjq1+fGN7XD5VKRVtPJ6IGd+T73YnKTPf2thqyCspZ9N+jTBvRhUG9fPhhTxJbDl7BSqNi1n3d\nCbj6TptXawd6d3bnZHwO8WmFdPZzpbBUx5fbL3DsQjatXWz50+ReeLjZs3LjGeIScnn5P4eoqNSj\nuzojy5Devg02w7YkTXqGWq0WH5/am8rLy4tTp05d937i4uLQ6/W0a2e+EKnx0GtbmTaiC6NC2ja6\nXg0hBMnaElq52CrvwZizcd9lpenodFIecyb0YMPVPpo/DAtQ+qfe+uo463YlkJ5TiuPVduZr8Wxl\njwpISC/k8Dktzg7WDQaL4cF+DO7lg02dCX41ajVd27oRl5BLXlEFGo2at78+oTT5dW3rxuBePrT1\ndEKjVqFWq2jtbKc83SdlFFFQomNgkLfSxq5SqZg1rjuvrz7M97sS6eLvZrZA+L8dl9DmlTE6pO01\nC5WGuDra0M7TiVbOtni1qg20Xf1dOXRWy6XUQnzaOPLdr/EYhcDGWs3nW89jY6UmrEdtsCutqFL6\nrEquTrdlb6uhracTgR3bEBna1qTAHNLbl0qdgdyiCiYO6qjUZK+HWq3iodFdG1zes1MbenRszfkr\n+ew4noaTfXWhWNMveP/ADlxKLeR0Uh7D+vg22GdpbtDGiUvZbD+SgndrB6aP6WZRLaZmiqjnPt5P\neWXtgBzv1g508XelXzdPetWZDeVP0b1YtOYIP+27DMBj47vjebXm9+i47iz9NpaVG8/w2swQk0mn\nzdFVGdhxLBV7WysG97p2oK2rZtAGgF8DgzUsYWutYVxYe9buuMTPh5OZ2641h85qOXAmk44+zozs\n35azSXnEJuTy/e5EurVtRWf/6t/9lcxizl3Jp4u/K8VlVfx8OIWOPi7EXJ2PMfSe2lrSfQPao1ar\nsLPW0KWtG77ujsTG5/DZ5nP89+cL/Lg3icJSHZ5u9syN6lHvVZpRIW05GZ/DtkPJdGvnxg97Eimv\nNNC1rRvzonoqv5M/P9CLDXsS2XYouTroBbShd2f3BoN3S9OkwetWzDOWlZXFc889xzvvvHPNdVs5\n27J25yU6+LtxbyNNKAaj4NDpDNbviufClXzcnG15ZVYo3drX78i9mJzP5oNX8Gxlz+A+fqzfFc8/\nvjyGEBAc6MmQkOpA4+HhTJ/DKZy8VN1WPXVkV9r513+SN8ejtYPyou7MyHss3q5GSA9v4hJyOZ9W\nxC+Hk8nMK2NYP3/yCiuIi8+pN3zXyd6a52f0p09XT7ZcnTpmWP92eHjUPkF7eMDz00N45ZN9rNx4\nlg+eGaaMWAM4cCqdmNh0Ovq6MDe69039KYoPnx2uzAZQY0AvP77YfpHk7FLSCyqITcilZ0AbHpvQ\nk5c/3senm89hY2dNhc7A8QtZnE7IRVdlwMnemgdHdWP0gPa4u9k1Wqj/cdw9N5zm6+Hp6aL8Tswt\nGxh87YctH3dHkrNKcHd3YvfxVFZuPIuNlZqXZoXS1teywiqwozv7TmXiaGfN4D7+BHfzpGdAm3oj\nE2t4eDjz4sxQ/v7pQQb38WPCsNp39iI8nLmYVsSmfUms3nqBF2eG1KtNGY0C9dVruu3AZYrLqoge\n3uW6f99t2jjhaGdFaYWeHp09TH6n1yt6VDe2H0lmx7FURoZ14MvtF7Cz0fDCI6H4ejgxYRicTcrl\n+Y/28u2ueN57aihqtYrPf74IwEP3dcfd1Z6F78ew8qczGAVEDQ3A39d08MSM8T1NPo/2dKFvdx/+\n+dVRziblMaSPH/On9DZb03Z3d2Ld7gSOX8zm+MVsHO2tmTu5B2PDO5jcIwBzo/vw+OTe9b7/PWjS\n4OXt7U16eu27BVqtFk9Pz0a2MFVSUsLcuXNZuHAhvXr1uub6rz0Wxgsf7eWfXx3jWaPR7BNIfGoh\nqzafRZtfPbS9W1s3LqYW8MK/9zF7XKDJ07yuysA/vzyK0Sh4ZGwg3du3orOPM//ZdJbCEh0TwtuT\nnV37NDxuQDtOXsrG2kpNeHdPk2WN8XS1IyuvDK9W9gR3bmPxdjXaXu3A/vTH0wCMCPbnqWnB5OSU\nkFVQzqGzWopLdRiMAl2VgUPntLy+8iAPjujMvtg0bKzU+Le2r3dcb1dbJgzsyA97k3jn8yPMmXAP\ndjZW5BdX8v7aE1hbqZk9rjsF+bd+Pjk7DTjaWRF3KZv4q4NRJg/uiIuthqem9Oa9tSf56LtYZX1f\nd0cGBnkzrI9fdS1Krycnp3potYeH83Xn6d2mrYcjh3NK+ft/DnDsQjZ2NhrmTOyBk7Xa4nML7+7B\nkL5j0FfolCCkK9eRXd5wc7uvmx3LFgzC3laj5GeN+8Pbk5RWwOGzmbzz+REeHd8dtUpFUkYRq7ec\nI6ugnF4B7oQGevJ9TCIateq67ou6Ovq4cDopDxdbzU1fy7Gh7fj6l0s8/9FedFUGZt4XiDVC2a+H\nkw1h93hx8KyWDTsvck+HVuw5mYa/hyNtW9ujUqmYProrqzafA2BANw+L07RwSm+0+WXVk3IXV1Ba\nbH64emRYez7+4TThPb2JHhaAi4MNeblN9xcYbuaB4E5p0uAVFBREcnIyaWlpeHh4sHnzZpYuXdrg\n+nVralVVVcyfP5+oqChGjx5t0fE6+7vxZFRPPlgXxwfr4njuj31NmrMOn9Py6aZzGI2Cwb18GDug\nHT5tHDmVmMsnP55m5cazxKcV0qNja/zcHdlxLI2M3DJG9vNXRvV1a9eKxY+FUVymqzdUuLO/K9HD\nAnB1tLmuYettPZ04nZRH9LCA6+oLqOHvWftkOqS3D9NG1T4he7rZc/+9HUzWH9rHj482nOLrqxP+\nBnf1aLDZZ/y9HbiQUsDJ+BzmLY3BzckGlUpFaYWe6aO73tAgDUuoVSo6+7kSe3XUVngPb6V5pbOf\nKwun9mZPXAZd/F3p0aE1rV3smiQdd4sO3i4cPpfFsQvZtPV0Yt6knibNrJZQq1S0drEju/L6Rts1\n1H9ibaVm/uQg3lt7kgNnMnGws8LWWsPWQ1cQAtq42HH0fBZHrza5DwryqdeXa6lpI7uQll16S67z\n0D6+bD2UTH5xJcFdPcw2Y06J6MyJSzl8vzuB+FR3jEIwJrSdcl8NDPKpfgfQKK7Zr1yXWq3Cp821\n75ngrh6s+OswpeYq1acSTfw3BGJiYli8eDFCCKKjo5kzZw4ffPABQUFBREREcOrUKRYsWEBRURG2\ntrZ4eHiwceNGfvrpJ1566SW6dOmijI76xz/+QWBg4+9PZGcXExObzpqt51EBfbq4Mzqk7dVO1kTs\nbDTMi+pJz06mM52n55Tywbo4sn7zsrFXawf+Nuvabfo3o6xCT0pWsdkBA5baE5tOdmEFUYM6olar\nrlnbyCuq4IPv40jWljBnwj2E3ePd4LrFZTq2HLxCanYp2rwycgsrCO7qwbxJPS0eMXYjth68wne7\nErC2UvOPOWE3XHC1hJpXSlYJiz4/SngPLx4a1dWk3/N6NEVelJRX8fZXx0m7Onm0u6sds8Z1J7Cd\nGylZJRw+l8UVbTEzxnQzmXj3Tjp+MZtD57N4eGQXk+bwujbtv6y8f9jK2Za354bf0MNlc9Aca15N\nHrxut5ob89iFLDYfuGIyJ1wrZ1v+MqU3bT3NDy6o0Ok5dyWf9JxS0nJKyS+qZOqIzg3OTXg3s6SQ\nqqwycCWzmC7+rtcVhPQGIxq1qkkDF0BqVgmvrz7MxEEdmXAD75DVaAnBC6rz/WYLz6bKi/ziSlb+\ndIa2Xk5MHtIJO5u7f7TbtfKiSm/glU8PkV1QwZSIgAYHUrUEMnjdBer+GIUQJKQVsf1oCuWVemaP\n637DzRbNTUspsEvKq3C0s7qpQNlS8uJWkHlRy5K8iE8tZPfJNP44qusNjUhtLppj8Gq5V4PqId+d\n/V2V4a5S81N3eiJJut1k+XH3apkNuJIkSVKLJoOXJEmS1OzI4CVJkiQ1OzJ4SZIkSc2ODF6SJElS\nsyODlyRJktTsyOAlSZIkNTsyeEmSJEnNjgxekiRJUrMjg5ckSZLU7MjgJUmSJDU7MnhJkiRJzY4M\nXpIkSVKzI4OXJEmS1OzI4CVJkiQ1OzJ4SZIkSc2ODF6SJElSsyODlyRJktTsyOAlSZIkNTtNHrxi\nYmIYO3YsY8aMYeXKlfWWHz16lMmTJ9OjRw+2b99usmzDhg2MGTOGMWPG8MMPPzR1UiVJkqRmwupa\nK6SkpLBu3ToOHTpEZmYmtra2BAYGMmbMGEaPHo2VVcO7MBqNLFq0iDVr1uDp6Ul0dDQjRowgICBA\nWcfX15e33nqLzz77zGTbwsJC/v3vf7NhwwaEEEyePJkRI0bg7Ox8E6crSZIktQSNBq/XXnuNM2fO\nMHbsWP7617/i7u5OZWUlCQkJ7N27l5UrV/K3v/2NPn36mN0+Li6O9u3b4+fnB0BkZCQ7duyoF7wA\nVCqVybZ79+5l4MCBSrAaOHAge/bsYdy4cTd+tpIkSVKL0GjwGjFiBG+88Ua977t168a4ceMoKCgg\nJSWlwe21Wi0+Pj7KZy8vL06dOmVRwsxtq9VqLdpWkiRJatkaDV5Dhw5tdGM3Nzfc3NwaXC6EuLFU\nNbDtb2tn5nh4yGbFGjIvasm8qCXzopbMi+brmn1eAG+99Rbz58/H3t6eGTNmcPbsWf7+978zceLE\nRrfz9vYmPT1d+azVavH09LQoYd7e3hw6dEj5nJmZSVhY2DW3y84utmj/LZ2Hh7PMi6tkXtSSeVFL\n5kWt5hjELRptuH//fpydndm7dy9eXl78/PPP9QZYmBMUFERycjJpaWnodDo2b97MiBEjGly/bm1r\n0KBB7N+/n+LiYgoLC9m/fz+DBg2yJLmSJElSC2dRzavGkSNHGDVqFF5eXhY14Wk0Gl599VVmz56N\nEILo6GgCAgL44IMPCAoKIiIiglOnTrFgwQKKior49ddf+eijj9i4cSOurq7MmzePBx54AJVKxYIF\nC3BxcbnhE5UkSZJaDpWwoGNq1qxZ+Pn5sW/fPn744QccHR2ZNGkSGzduvB1pvC6yGaCabBKpJfOi\nlsyLWjIvarXYZsP33nuPzp07s2zZMlxdXcnMzGTWrFlNnTZJkiRJMsuiZsPWrVszc+ZM5bO/vz/+\n/v5NlSZJkiRJalSjwSssLKzRvq0DBw7c8gRJkiRJ0rU0Gry+//57ANatW0dBQQFTp05FCMH333+P\nl5fXbUmgJEmSJP1Wo8GrZlqnI0eO8OWXXyrfv/LKKzz88MM8/vjjTZs6SZIkSTLDogEbWVlZ5OXl\nKZ/z8vLIzs5uskRJkiRJUmMsGrDxyCOPEBUVxbBhwwDYvXs3TzzxRFOmS5IkSZIaZFHweuihh+jX\nrx9HjhxBCMFDDz1Et27dmjptkiRJkmSWxTNsBAYGEhgY2JRpkSRJkiSLWBS8jh8/zrvvvktKSgoG\ngwEhBCqVSg6VlyRJku4Ii4LXyy+/zLx58+jTpw9qtUVjPCRJkiSpyVgUvOzs7Lj//vubOi2SJEmS\nZBGLqlFDhgxh9+7dTZ0WSZIkSbKIRTWvb775hhUrVuDo6IiNjY3s85IkSZLuKIuCV800UZIkSZJ0\nN7AoePn5+aHX60lKSkKlUtGhQwesrK7r71hKkiRJ0i1jUQQ6deoUf/7zn5UmQ71ez4cffkiPHj2a\nOn2SJEmSVI9FwWvx4sUsWbKE8PBwAA4ePMiiRYtYu3ZtkyZOkiRJksyxaLRheXm5Erig+u98lZeX\nN1miJEmSJKkxFgUve3t7Dh48qHw+fPgw9vb2TZYoSZIkSWqMRc2GL730Ek899RQ2NjYAVFVV8cEH\nH1h0gJiYGJYsWYIQggceeIA5c+aYLNfpdDz//POcOXOGVq1asWzZMnx9fdHr9bzyyiucOXMGo9HI\nxIkT620rSZIk/T5ZFLx69erF9u3bSUpKQghBp06dsLa2vuZ2RqORRYsWsWbNGjw9PYmOjmbEiBEE\nBAQo66xbtw5XV1e2b9/Oli1bePfdd1m2bBnbtm2jqqqKjRs3UlFRwbhx4xg/fjy+vr43fraSJElS\ni2BRs+H+/fupqKiga9eudOvWjfLycoteUI6Li6N9+/b4+flhbW1NZGQkO3bsMFlnx44dTJo0CYAx\nY8YozZMqlYqysjIMBgPl5eXY2Njg5OR0vecnSZIktUAWBa933nnHJHA4OTnxzjvvXHM7rVaLj4+P\n8tnLy4usrCyTdbKysvD29gZAo9Hg7OxMQUEBY8aMwd7enkGDBjF8+HAeffRRXFxcLDopSZIkqWWz\nqNmwZjqoGmq1GoPBYNF217tOzbHi4uLQaDTs27ePgoIC/vjHPxIeHo6/v78lSZYkSZJaMIuCl6Oj\nI7GxsfTu3RuA2NhYHBwcrrmdt7c36enpymetVounp2e9dTIzM/Hy8sJgMFBSUoKrqyubNm1i8ODB\nqNVqWrduTXBwMKdPn75m8PLwcLbklH4XZF7UknlRS+ZFLZkXzZdFwevZZ59l/vz5dO7cGYD4+Hg+\n+uija24XFBREcnIyaWlpeHh4sHnzZpYuXWqyTkREBBs2bKB3795s27aNsLAwAHx8fDh48CATJkyg\nrKyM2NhYZs6cec1jZmcXW3JKLZ6Hh7PMi6tkXtSSeVFL5kWt5hjEVcKStj2gsLCQkydPIoSgb9++\nuLq6WnSAmJgYFi9ejBCC6Oho5syZwwcffEBQUBARERHodDqeffZZzp07h5ubG0uXLsXf35+ysjJe\nfNEYr50AABg9SURBVPFFEhISAHjggQeYNWvWNY8nf4zV5I1ZS+ZFLZkXtWRe1GrRwSspKYmEhARG\njhxJaWkpVVVVuLm5NXX6rpv8MVaTN2YtmRe1ZF7UknlRqzkGL4tGG27YsIEnn3ySf/zjH0B139Vf\n/vKXJk2YJEmSJDXEouD1+eef8/333+PsXB2dO3XqRE5OTpMmTJIkSZIaYlHwsra2xtHR0eQ7jUbT\nJAmSJEmSpGuxKHi5ubkpf4gS4Mcff1ReLJYkSZKk283iiXmfeeYZkpKSGD58OHZ2dnzyySdNnTZJ\nkiRJMsui4NWxY0e+++47Ll++jBCCjh07ymZDSZIk6Y6xqNkwKSkJvV5PQEAAGRkZrFq1isLCwqZO\nmyRJkiSZZVHw+stf/oJarSYlJYXXX3+dlJQUnn/++aZOmyRJkiSZZVHwUqvVWFtbs3v3bqZNm8ai\nRYvIyMho6rRJkiRJklkWBa/Kykq0Wi07d+5U5h60cGIOSZIkSbrlLApejzzyCJGRkTg6OhIUFERK\nSorywrIkSZIk3W4Wz21Yl8FgwGAwYGNj0xRpuilyrrJqct62WjIvasm8qCXzolaLm9vw9OnTZr/X\naDTY2Nig0+mUWd8lSZIk6XZp9D2vFStWUF5ezvjx4+nduzfu7u5UVlaSlJTEnj172L17Ny+88AIB\nAQG3K72SJEmS1Hjw+vDDD4mLi+Obb77h3//+N5mZmdjb29O1a1dGjhzJV199hZOT0+1KqyRJkiQB\nFsyw0atXL3r16nU70iJJkiRJFrFotKEkSZIk3U1k8JIkSZKaHRm8JEmSpGZHBi9JkiSp2bEoeOXm\n5vLXv/6Vhx56CIDz58/zf//3f02aMEmSJElqiEXB65VXXqFfv34UFRUB0KlTJ77++muLDhATE8PY\nsWMZM2YMK1eurLdcp9Px9NNPM3r0aKZOnUp6erqy7Pz58zz44IOMHz+eCRMmoNPpLDqmJEmS1LJZ\nFLy0Wi3Tpk1T/gCljY0NavW1NzUajSxatIhVq1axadMmNm/eXG9GjnXr1uHq6sr27dt55JFHePfd\nd4HqKaiee+453njjDTZt2sQXX3yBtbX19Z6fJEmS1AJZFLysrExfBysqKrJoVvm4uDjat2+Pn58f\n1tbWREZGsmPHDpN1duzYwaRJkwAYM2YMBw8eBGDv3r0EBgbStWtXAFxdXVGpVJYkV5IkSWrhLApe\no0eP5rXXXqO0tJT169cze/ZsHnjggWtup9Vq8fHxUT57eXmRlZVlsk5WVhbe3t5A9ZyJzs7OFBT8\nf3v3HhxVef9x/L1sAlJMgpiQRaS0JraQGqAzKsERIYBZIITsBiIMUsKlpdoBKqFYwck4crXGyUhk\nOhIBKzRMa4HIJRBSgxI6XGy1hZkCRUEn3JJwS5NgypLN8/sjP3YbgrBWNvEkn9df7Nlnz373yzN8\nOGfPPqeKL774AoAZM2aQlpbG6tWrA/1MIiLSxt12hQ2An/70p2zdupXq6mr27NnDT37yE1JTU2/7\nukCOzm4cY4zBZrPh9Xr55JNP2LRpE506dWLq1Kk89NBDvvuJiYhI+xVQeAGMHTuWsWPHfq2dOxyO\nJhdgVFRU0L1792ZjysvLiY6Oxuv1UltbS0REBA6Hg0ceeYSIiAgAnnjiCY4cOXLb8LLi0v7Bol74\nqRd+6oWfemFdAYXXxYsX+f3vf09ZWRn19fW+7StWrLjl6+Lj4ykrK+PMmTNERUVRWFhITk5OkzGJ\niYkUFBTQv39/ioqKfOH0+OOPs3r1aq5evYrdbuevf/0rU6dOvW2tuj9PI92ryE+98FMv/NQLPyuG\neEDh9Ytf/IK4uDgGDRrku+IwEHa7naysLKZPn44xhvHjxxMTE0Nubi7x8fEkJiaSnp7O/PnzSUpK\nomvXrr5wCw8PZ9q0aYwbNw6bzcbQoUMZMmTI//YpRUSkTQnoTspjx45l69atLVHPN6b/STXS/yr9\n1As/9cJPvfCz4pFXQFcb9u/fn3/961/BrkVERCQgAZ02nDhxIpMnT8bhcNCpUyff9o0bNwatMBER\nka8SUHjNnz+fZ555hri4uK/1nZeIiEgwBBRenTp1YsaMGcGuRUREJCABfec1ePBgSktLg12LiIhI\nQAI68nr33XfJy8ujS5cudOzY0bcKxv79+4Ndn4iISDMBhdemTZuCXYeIiEjAAgqvnj17BrsOERGR\ngN0yvObPn092drZvlYsb6VJ5ERFpDbcMr4yMDAB+/etft0gxIiIigbhleG3YsIFly5bx6KOPtlQ9\nIiIit3XLS+WPHj3aUnWIiIgELKDfeYmIiHyb3PK04fHjxxk0aFCz7fqdl4iItKZbhtf3vvc98vLy\nWqoWERGRgNwyvDp27KjfeImIyLfOLb/zCg0Nbak6REREAnbL8Hr33Xdbqg4REZGA6WpDERGxHIWX\niIhYjsJLREQsJ+jhVVpaysiRI3E6nTe97N7j8TB37lySkpKYMGECZ8+ebfL82bNn+fGPf8zbb78d\n7FJFRMQighpeDQ0NLF68mDVr1rB9+3YKCws5ceJEkzEbN24kIiKC4uJiMjIyyM7ObvL8K6+8wpAh\nQ4JZpoiIWExQw+vw4cP07t2bnj17EhoaSnJyMiUlJU3GlJSU4Ha7AXA6nU1W7Xj//ffp1asXsbGx\nwSxTREQsJqjhVVFRQY8ePXyPo6OjqaysbDKmsrISh8MBgN1uJzw8nKqqKurq6li9ejWzZs0KZoki\nImJBAd1J+X9ljPnaY66vm5ibm8vUqVPp3LlzwPsCiIoK+/qFtlHqhZ964ade+KkX1hXU8HI4HE0u\nwKioqKB79+7NxpSXlxMdHY3X66W2tpaIiAgOHz5McXEx2dnZVFdX06FDBzp16sTTTz99y/c8f74m\nKJ/FaqKiwtSL/6de+KkXfuqFnxVDPKjhFR8fT1lZGWfOnCEqKorCwkJycnKajElMTKSgoID+/ftT\nVFREQkICAPn5+b4xK1eupEuXLrcNLhERaR+CGl52u52srCymT5+OMYbx48cTExNDbm4u8fHxJCYm\nkp6ezvz580lKSqJr167Nwk1ERORGNhPol0kWodMAjXRKxE+98FMv/NQLPyueNtQKGyIiYjkKLxER\nsRyFl4iIWI7CS0RELEfhJSIilqPwEhERy1F4iYiI5Si8RETEchReIiJiOQovERGxHIWXiIhYjsJL\nREQsR+ElIiKWo/ASERHLUXiJiIjlKLxERMRyFF4iImI5Ci8REbEchZeIiFiOwktERCxH4SUiIpYT\n9PAqLS1l5MiROJ1O8vLymj3v8XiYO3cuSUlJTJgwgbNnzwKwb98+0tLSGDt2LOPGjePAgQPBLlVE\nRCwiqOHV0NDA4sWLWbNmDdu3b6ewsJATJ040GbNx40YiIiIoLi4mIyOD7OxsALp168aqVavYunUr\nr7zyCs8//3wwSxUREQsJangdPnyY3r1707NnT0JDQ0lOTqakpKTJmJKSEtxuNwBOp5P9+/cD0KdP\nH6KiogB48MEH8Xg8XLt2LZjlioiIRQQ1vCoqKujRo4fvcXR0NJWVlU3GVFZW4nA4ALDb7YSHh1NV\nVdVkTFFREXFxcYSGhgazXBERsYiQYO7cGPO1xxhjsNlsvseffvopOTk5rF27NqD3jIoK+3pFtmHq\nhZ964ade+KkX1hXU8HI4HL4LMKDxSKx79+7NxpSXlxMdHY3X66W2tpaIiAgAysvLmTVrFq+++ir3\n339/QO95/nzNnfsAFhYVFaZe/D/1wk+98FMv/KwY4kE9bRgfH09ZWRlnzpzB4/FQWFjI8OHDm4xJ\nTEykoKAAaDw9mJCQAEB1dTU///nP+dWvfsWAAQOCWaaIiFhMUMPLbreTlZXF9OnTGTNmDMnJycTE\nxJCbm8sHH3wAQHp6OpcvXyYpKYl33nmHefPmAZCfn09ZWRm//e1vcblcuN1uLl26FMxyRUTEImwm\nkC+mLESnARrplIifeuGnXvipF346bSgiItICFF4iImI5Ci8REbEchZeIiFiOwktERCxH4SUiIpaj\n8BIREctReImIiOUovERExHIUXiIiYjkKLxERsRyFl4iIWI7CS0RELEfhJSIilqPwEhERy1F4iYiI\n5Si8RETEchReIiJiOQovERGxHIWXiIhYTtDDq7S0lJEjR+J0OsnLy2v2vMfjYe7cuSQlJTFhwgTO\nnj3re27VqlUkJSUxatQo/vKXvwS7VBERsYighldDQwOLFy9mzZo1bN++ncLCQk6cONFkzMaNG4mI\niKC4uJiMjAyys7MB+Oyzz9i5cyc7duzgrbfe4uWXX8YYE8xyRUTEIoIaXocPH6Z379707NmT0NBQ\nkpOTKSkpaTKmpKQEt9sNgNPp5MCBAwDs3r2b0aNHExISwv3330/v3r05fPhwMMsVERGLCGp4VVRU\n0KNHD9/j6OhoKisrm4yprKzE4XAAYLfbCQsLo6qq6qavraioCGa5IiJiEUENr0BO891sjM1m+8rt\nIiIiIcHcucPhaHIBRkVFBd27d282pry8nOjoaLxeLzU1NUREROBwODh37pxvXHl5ebPX3kxUVNid\n+wAWp174qRd+6oWfemFdQT3yio+Pp6ysjDNnzuDxeCgsLGT48OFNxiQmJlJQUABAUVERCQkJAAwb\nNowdO3bg8Xg4deoUZWVl9OvXL5jlioiIRQT1yMtut5OVlcX06dMxxjB+/HhiYmLIzc0lPj6exMRE\n0tPTmT9/PklJSXTt2pWcnBwAYmNjGTVqFMnJyYSEhPDSSy/ptKGIiABgM7r+XERELEYrbIiIiOUo\nvERExHIUXiIiYjltJrxut4ZiW1ZeXs6UKVMYPXo0KSkprFu3DoB///vfTJ8+HafTyYwZM6ipqWnl\nSltOQ0MDbrebZ555BoDTp0/z1FNP4XQ6yczMpL6+vpUrbBk1NTXMmTPHd/HToUOH2u28+N3vfseY\nMWNISUlh3rx5eDyedjMvFi5cyGOPPUZKSopv263mwZIlS0hKSiI1NZWjR4+2Rsm31SbCK5A1FNsy\nu93OggUL2LFjB3/4wx/Iz8/nxIkT5OXlMWjQIHbt2sXAgQNZtWpVa5faYtatW0dMTIzv8Wuvvca0\nadPYtWsXYWFhbNy4sRWrazlLly5lyJAh7Ny5ky1btvDAAw+0y3lRUVHB+vXr2bx5M9u2bcPr9VJY\nWNhu5kVaWhpr1qxpsu2r5sGePXsoKyujuLiYRYsW8dJLL7VGybfVJsIrkDUU27KoqCj69u0LQJcu\nXYiJiaGioqLJupFut5v333+/NctsMeXl5ezZs4f09HTftgMHDuB0OoHGXvz5z39urfJaTG1tLX/7\n298YN24cACEhIYSFhbXbedHQ0EBdXR319fX85z//oXv37hw8eLBdzIuHH36Y8PDwJttunAfX/80s\nKSnB5XIB0L9/f2pqarhw4ULLFhyANhFegayh2F6cPn2aY8eO0b9/fy5evEhkZCTQGHCXL19u5epa\nxrJly3j++ed9vwu8fPkyERERdOjQON0dDke7mB+nT5/mnnvuYcGCBbjdbrKysqirq2uX8yI6Oppp\n06YxdOhQnnjiCcLCwoiLiyM8PLzdzYvrLl261GQeXLp0CWi63ix8e9eVbRPhpZ+qNbpy5Qpz5sxh\n4cKFdOnSpV3+qPvDDz8kMjKSvn37+uaFMabZHGkPvamvr+fIkSNMmjSJgoICOnfuTF5eXrv47Deq\nrq6mpKSEDz74gL1791JXV0dpaWmzce2xNzeyyrqyQV1ho6UEsoZiW1dfX8+cOXNITU1lxIgRANx7\n771cuHCByMhIzp8/T7du3Vq5yuD75JNP2L17N3v27OHq1atcuXKFZcuWUVNTQ0NDAx06dAh4nUyr\nczgcOBwO4uPjAUhKSuKtt95ql/Ni37599OrVi65duwIwYsQI/v73v1NdXd3u5sV1XzUPoqOjKS8v\n9437tvalTRx5BbKGYlu3cOFCYmNjycjI8G0bNmwYmzdvBqCgoKBd9CQzM5MPP/yQkpIScnJyGDhw\nIK+99hoDBw6kqKgIaD+9iIyMpEePHnz++edA4/d+sbGx7XJe3HfffRw6dIirV69ijOHAgQM8+OCD\n7Wpe3HhE9VXzYPjw4bz33nsA/OMf/yA8PNx3evHbpM0sD1VaWsrSpUt9ayjOnDmztUtqMR9//DGT\nJ0/mBz/4ATabDZvNxty5c+nXrx/PPfcc586d47777mPFihXNvrRtyz766CPWrl3Lm2++yalTp8jM\nzKS6upq+ffuSnZ1NaGhoa5cYdMeOHePFF1+kvr6eXr16sXz5crxeb7ucFytXrqSwsJCQkBDi4uJY\nsmQJ5eXl7WJezJs3j4MHD1JVVUVkZCSzZ89mxIgR/PKXv7zpPFi0aBF79+6lc+fOLF++nB/96Eet\n/AmaazPhJSIi7UebOG0oIiLti8JLREQsR+ElIiKWo/ASERHLUXiJiIjlKLxERMRyFF5iScOGDeOz\nzz5rkfdauXJlk1tlLFiwgPz8/G+83wULFpCSkkJmZuY33tetHDt2jJ07dwb1PURamsJL5DZWrlzJ\ntWvX7ug+L1y4QHFxMdu2bSMnJ+eO7vtGR44c+Z/Dq6Gh4Q5XI3JnKLykTfn888/52c9+Rnp6Oi6X\ny7f8DUCfPn1YtWoV48eP58knn6S4uNj33K5duxg1ahRpaWmsWrWKPn36UFdXx6JFi7DZbEycOBG3\n201tbS0Ax48fJyMjA6fTyQsvvPCV9bz33nukpKSQmprK7NmzuXTpEleuXCEjI4OrV6/idrt55513\nmrxmy5YtzJo1y/fY6/UyePBg3/qdq1ev5qmnniItLY1nn32WixcvAnDt2jV+85vfkJKSgsvlYvbs\n2VRVVfHGG29w4MAB3G43S5cuBRpXpHG73aSmpjJt2jROnToFNK5K4nK5WLJkCRMnTmTv3r3f5K9D\nJHiMiAUlJiaaTz/9tMm2+vp643a7zcmTJ40xxtTW1hqn0+l7/MMf/tDk5+cbY4z5+OOPzeDBg40x\nxly4cME8+uijpqyszBhjzNtvv2369OljvvzyS9/r6urqfO/zwgsvmEmTJhmPx2M8Ho9JTk42+/bt\na1bj8ePHzeOPP24uXLhgjDHm9ddfN88995wxxpjTp0+bhISEm362uro6k5CQYC5fvmyMMWb37t0m\nIyPDGGPMli1bTFZWlm/shg0bzLx584wxxrzxxhtm9uzZpr6+3hhjfK/fvHmzmTNnju81Fy9eNAkJ\nCebEiRPGGGP+9Kc/mfT0dGOMMQcPHjRxcXHm0KFDN61N5NtCR17SZnzxxRecPHmSzMxMXC4XTz/9\nNNeuXWtyV+3Ro0cDMGDAAM6fP4/H4+HQoUM89NBD9OrVC4Dx48c327e5YRW1ESNGEBoaSmhoKHFx\ncZSVlTV7zcGDBxk6dCj33nsvABMnTmTfvn23/Rx33XUXw4cPZ/v27UDjoqnXbyi5e/du9u/fj8vl\nwuVysWHDBs6dOwc03g5mypQp2O12AN8K6jc6dOgQffv25YEHHgBg3LhxHD16lC+//BKA3r17069f\nv9vWKdKa2sQtUUSgMWC6detGQUHBTZ+32Wx06tQJwHcDQq/X2yyYbnx8Mx07dvT92W63N7mg47/3\nc+N9kK6/7+24XC6WL1/OmDFj+Oijj8jOzvbt89lnnyUtLe2m7xeIm9X134+/853vBLQfkdakIy9p\nM77//e9z1113sWXLFt+2kydPcuXKFaD5P+7XHw8YMIB//vOfvu99/vt7MoC7776bmpqar13PoEGD\n2LNnj+87qT/+8Y889thjzd7/Zh5++GFqa2vJycnhySef9IXusGHD2LBhA9XV1QB4PB6OHTsGQGJi\nIuvWrfNdXHL9Dsl3332377u665/36NGjvlulbN68mbi4OIWWWIqOvMSSbDYbU6dOJSQkxHcksW3b\nNt58802WLl3K2rVr8Xq9REZG8vrrr/tec+M+oPGmfC+//DIzZ87knnvuYejQoYSEhNC5c2cApk2b\nxpQpU+jcuTPr168PuMbY2FgyMzOZOnUqHTp0oFevXixatKjZ+38Vl8tFbm4uGzZs8G1LTU2lqqqK\nyZMnY7PZaGhoYNKkSfTp04eZM2eSk5ODy+WiY8eOfPe732XFihUMGjSINWvW4HK5eOSRR3jxxRd5\n9dVXmTdvHl6vl27duvmO7ESsQrdEEQGuXLlCly5dgMYjkU2bNt2R33KJSHDoyEsEWL9+PUVFRXi9\nXrp27crixYtbuyQRuQUdeYmIiOXogg0REbEchZeIiFiOwktERCxH4SUiIpaj8BIREctReImIiOX8\nH4gzFtcS9o9MAAAAAElFTkSuQmCC\n",
+ "text/plain": [
+ "\u003cmatplotlib.figure.Figure at 0x7f47b20dd690\u003e"
+ ]
+ },
+ "metadata": {
+ "tags": []
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "plt.plot(graph_means)\n",
+ "plt.ylabel('Time (seconds)')\n",
+ "plt.xlabel('Length of vector')\n",
+ "_ = plt.title('Time to sum the elements of 1000 vectors (vectorized TF operation)')\n",
+ "_ = plt.ylim(ymin=0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "4KZg2WXjbhg5"
+ },
+ "source": [
+ "## AutoGraph"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {
+ "autoexec": {
+ "startup": false,
+ "wait_interval": 0
+ }
+ },
+ "colab_type": "code",
+ "id": "UQJBQWbCbinm"
+ },
+ "outputs": [],
+ "source": [
+ "# Sum written using for loop and converted with AutoGraph\n",
+ "def sum_all(elements):\n",
+ " sum_ = 0.0\n",
+ " length = len(elements)\n",
+ " for i in tf.range(length): \n",
+ " sum_ += elements[i][0]\n",
+ " return sum_\n",
+ "\n",
+ "def run_trial(num):\n",
+ " elements = get_elements(num)\n",
+ " return sum_all(elements)\n",
+ " \n",
+ "ag_means = []\n",
+ "ag_run_trial = ag.to_graph(run_trial)\n",
+ "\n",
+ "for num in range(max_elements):\n",
+ " with tf.Graph().as_default():\n",
+ " durations = []\n",
+ " foo = ag_run_trial(num)\n",
+ " with tf.Session() as sess:\n",
+ " for _ in range(burn_ins):\n",
+ " for _ in range(batches):\n",
+ " sess.run(foo)\n",
+ " \n",
+ " for _ in range(trials):\n",
+ " start = time.time()\n",
+ " for _ in range(batches):\n",
+ " sess.run(foo)\n",
+ " \n",
+ " duration = time.time() - start\n",
+ " durations.append(duration)\n",
+ " ag_means.append(np.mean(durations))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {
+ "autoexec": {
+ "startup": false,
+ "wait_interval": 0
+ },
+ "height": 301
+ },
+ "colab_type": "code",
+ "executionInfo": {
+ "elapsed": 310,
+ "status": "ok",
+ "timestamp": 1532448438694,
+ "user": {
+ "displayName": "",
+ "photoUrl": "",
+ "userId": ""
+ },
+ "user_tz": 240
+ },
+ "id": "DLDOmrRW99v5",
+ "outputId": "ae0e0573-39db-4004-a064-efc618dbf867"
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYwAAAEcCAYAAADUX4MJAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XdYVFf++PH3DE1AinTEjgULioggNiTYjcZCoiZqjEmM\n6RuzcVc32exPE7O72dTNmmhi1u+abLJqLFFsib1E7GLDjkgbQUSKyDAz5/eHySARcYwMQ/m8nsfn\nce4999zPPXOHzz23nKtRSimEEEKIu9DaOgAhhBC1gyQMIYQQFpGEIYQQwiKSMIQQQlhEEoYQQgiL\nSMIQQghhkTqZMObPn88bb7xh6zBqlQceeICffvrJ6uuZOXMmH330kdXXU1NcuHCBUaNG0a1bN776\n6itbh1OnnT17ljFjxtg6jGpV2e9Jr9czZMgQcnNzq2x9tTJhdO3alfDwcMLDw2nfvj1dunQxT1uz\nZg3PPPMMc+bMsXoc6enphISEYDKZrL6uqlTf/mjfjTW/xy+++IKoqCgOHDjAhAkTbpu/bt06xo0b\nR1hYGJMmTbpt/smTJxk9ejRhYWGMGTOG5OTkcvPfffddoqKi6NGjB+++++49LWttEydOZNmyZdW2\nvo8//pinnnqqwjgiIyMpLS29p/pCQkK4dOnSPS3z1VdfMWLECMLCwujduzeTJk1i7dq191RHVXF0\ndCQ+Pp7PP/+8yuqslQnj0KFDHDx4kIMHD9K4cWPmz59vnvbggw9WWxxKKTQaDfLsY+1mze8xIyOD\n1q1b33G+p6cnkydPZurUqbfNKy0t5fnnn2fkyJHs27ePkSNH8txzz2EwGAD49ttv2bx5M6tXr+b7\n779n69at/O9//7No2drgXr6P7OxsEhMTiYuLKzc9PT2dAwcOoNFo2Lx58z2tX6PR3FP5OXPmsHjx\nYmbOnMnevXvZsWMHv/vd79ixY8cdl7H2344HH3yQFStW3HOyvJNamTBupZS6rdE/+eQTXnvtNaDs\n6HH58uX069ePqKgovv32W44ePcqIESOIjIy8rTeybNkyhg4dSlRUFE899RQZGRkVrnvixIkARERE\nEB4ezpEjR1BKMW/ePB544AF69erFH//4RwoLCytc/urVq0ybNo3u3bsTFRVV7gj010c3t/YK9u7d\nS0xMDF988QU9e/akT58+/Pjjj2zbto1BgwYRFRXF/PnzK1znkiVLWL16NV988QXh4eE8++yz5nkn\nT55kxIgRdO/enenTp6PX683ztmzZwsiRI+nevTvjx4/n1KlTFdYPcO7cOaZMmUJUVBRDhgxh3bp1\ndyxbWb0PPPAACxcuZMSIEXTt2pXXX3+dK1eu8PTTTxMeHs6UKVMoKCgwlz98+DDjxo2je/fujBw5\nkr1795rnTZw4kY8++ojx48cTHh7Ok08+SV5ennkelP8eU1NTmThxIhEREURHRzN9+vQ7bsOmTZt4\n8MEHiYyMZNKkSZw/fx6Axx9/nMTERGbPnk14eDgXL168bdno6GgGDx6Mr6/vbfP27t2L0Whk0qRJ\nODg4MHHiRJRS7NmzB4CVK1cyZcoU/Pz88PPz44knnmDFihUAJCYmVrrsrdauXXvbqZxFixbx3HPP\nATdPbfztb38jNjaW3r1785e//KXcvvHjjz8ycuRIunXrxsCBA9m5cycffPABBw4cYM6cOYSHh/PW\nW28BcPDgQeLj4+nevTsPP/wwhw4dKvcdffDBB4wfP56wsDDS0tJYvnw5/fv3Jzw8nP79+7NmzZoK\nv4Ndu3bRsWNHHB0dy01fuXIlYWFhjB492tw2t67v1h7QihUrePTRRwGYMGECSilGjBhBeHi4eR9e\nsmQJAwcOJCoqiueee47Lly8DN089fvPNN3zwwQdER0fj6OiIRqMhPDycd955567bOHToUMLDwxkw\nYIA56f+yD8TExDB//nx69OhBXFwcq1evLrcd165d45lnniE8PJyxY8eW+7vh7++Ph4cHR44cqbDd\n7pmq5WJjY9Xu3bvLTfvnP/+pXnvtNaWUUmlpaapdu3bqzTffVCUlJWrXrl0qNDRUPf/88yo3N1dl\nZWWp6OhotW/fPqWUUj/88IMaOHCgOn/+vDIajerTTz9VY8eOrXDdaWlpKiQkRJlMJvO0pUuXqoED\nB6q0tDR1/fp19cILL5hj+bX33ntPvfnmm8poNCqDwaD2799vnhcSEqJSU1PNn//4xz+qDz/8UCml\nVGJiourQoYOaN2+eMhgMasmSJapHjx7q1VdfVdevX1dnzpxRoaGh6tKlSxWu99a6bm3Hhx9+WGVn\nZ6tr166pIUOGqG+//VYppdSxY8dUdHS0SkpKUiaTSa1YsULFxsYqvV5/W93Xr19XMTExasWKFcpk\nMqkTJ06oqKgodfbs2dvWfbd6Y2Nj1dixY9WVK1eUTqdT0dHRatSoUerkyZNKr9erSZMmqU8++UQp\npVRWVpaKjIxU27dvV0optXv3bhUZGalyc3OVUkpNmDBBDRgwQF28eFGVlJSoCRMmqPfee++O3+P0\n6dPVZ599ppRSqqSkRB04cKDCtjx//rwKCwtTu3fvVgaDQX3++edqwIABqrS01LzepUuXVrjsrZYs\nWaImTpxYbtq///1v9fTTT5eb9swzz6h///vfSimlunXrpo4cOWKed/ToURUeHm7RsrcqLi5W4eHh\n6uLFi+ZpY8aMUWvXrlVKKfXWW2+pZ599VuXn56uioiI1bdo09f777yullDpy5Ijq1q2b+Teo0+nU\n+fPnK9z2vLw81b17d/X9998ro9Go1qxZo7p3767y8vLM5WNjY9XZs2eV0WhUBQUFKjw8XKWkpCil\nlMrOzjbvR7/2t7/9Tc2ePfu26QMGDFDffPONOnbsmOrYsaO6cuWKed6v41u+fLl69NFHzZ/btWtX\n7je4e/duFRUVZd7/5syZox577DGllFLffPONeuCBByqM7Va/3sbS0lK1detW82913759qkuXLurE\niRNKqbLf+l//+lel1+vV3r17VVhYmLpw4YJS6ubvKTIyUh09elQZjUb16quvqunTp5db57Rp09Ti\nxYvvGpslan0PwxIajYbnn38eR0dHevbsibOzM8OGDaNRo0b4+/sTERHBiRMnAPjf//7H1KlTadmy\nJVqtlqlTp5KcnExmZuYd61e39HDWrFnD5MmTCQoKwtnZmenTp7N27doKz4/b29uTnZ1NWloadnZ2\ndOvWrcI6K+Lg4MC0adOws7Nj6NChXL16lccffxxnZ2dat25N69atK+0FVGTSpEn4+Pjg7u5ObGws\nJ0+eBGDp0qWMGzeO0NBQNBoNI0eOxNHRscKjli1bttCkSRNGjhyJRqOhffv2DBw4kPXr199W1pJ6\nJ0yYgJeXF35+fkRERNClSxdCQkJwcHBgwIAB5hi///57+vXrR58+fYCbR+6dOnVi27Zt5rpGjx5N\ns2bNcHR0ZMiQIeZlf3Frm9vb25Oeno5Op8PR0ZHw8PAK22zdunX069eP6Oho7OzsePLJJ7lx40a5\nI+ff6vr167i5uZWb1rBhQ3OP9dfz3dzcuH79ukXL3qpBgwbExcWZj95TUlK4cOGC+fTOsmXLmDlz\nJm5ubri4uDB16lRz2WXLlhEfH090dDQAfn5+tGzZssLt2bp1Ky1atGD48OFotVqGDRtGq1at2LJl\ni7nMqFGjCA4ORqvVYmdnh52dHadPn6akpAQfHx+Cg4MrrLugoABXV9dy0/bv309GRgZDhgyhY8eO\nNGvW7Laj83uxZs0a4uPjzfvf9OnTOXz4MBkZGVy9evW2XmJMTAzdu3enc+fO5f5+3LqN9vb2xMTE\n0KRJE+BmL7dXr17s37/fXF6j0fC73/0OBwcHunfvTkxMTLle+8CBA+nUqRNarZbhw4fftl+7urqS\nn5//m7f7VvZVUkst4O3tbf5/gwYN8PHxMX92cnIy/9AyMjJ4++23+dvf/gaUnd/W6XQEBgbedT2X\nL1+mcePG5s9BQUEYDAZycnLw8/MrV/app57in//8J1OmTEGj0fDwww9XeC67Ip6enuZzrA0aNKhw\nG3/ZJkvduryzszPZ2dnAzTZZtWqV+S4fpRQGg8HcHb9VRkYGhw8fJjIy0lzWaDQycuTICsverd5b\nY3Jycrrt863f27p168x/fH6p65c/ZEC579zZ2bnS9pkxYwYffvgh8fHx5usMFd2B8+vvW6PREBgY\niE6nu2PdlnJxcbntD3xhYSENGzascH5hYSEuLi4WLftrw4YN4+9//zvPPfcca9asoX///jg6OpKb\nm0txcXG5bTeZTObkmpWVRUxMjEXb8+u2AmjcuHG5tgoICDD/39nZmQ8++ICFCxcya9YsunXrxowZ\nM2jVqtVtdbu7u1NUVFRu2qpVq+jduzceHh7mbVy5ciWPP/64RfFWFH/Hjh3Nn11cXPD09ESn0+Hp\n6Xnb72Hbtm0YjUY6depU7mDk1m38pdy8efNISUnBZDJx48YN2rVrV27bnJyczJ8bN25cbl1326+L\niopwd3f/Tdv8a/UmYVgqICCAZ5991qKL5xVdFPPz8yt3zSM9PR17e/tyX+ovXFxc+MMf/sAf/vAH\nzp07x8SJE+ncuTM9evTA2dmZ4uJic9ns7OzbdrTqEhAQwLRp03jmmWfuWjYwMJCoqCgWLlxYpfVa\nst6RI0cye/bse162ou/R29vbfG3rwIEDPPHEE0RGRtK0adNy5fz8/Dhz5ky5aZmZmVXyXbVp04ZF\nixaVm3b69GnzNZfWrVuTnJxMaGgocPMaVJs2bSpdtqI7tQB69+7NzJkzSU5OJiEhgVmzZgHQqFEj\nnJ2dWbNmzW0HPHDzO7zTnUS/blc/Pz82btxYblpGRgZ9+/a94zK9evWiV69e6PV6PvjgA9544w2+\n/vrr29bVrl07Vq1aZf5cUlLCunXrMJlM9O7dG7h5I0B+fj6nTp2iXbt2uLi4cOPGDfMyvxwg3cmv\nf9vXr18nLy8Pf39/PD09eeuttzh+/Hi5pAK3ny24dRv1ej0vv/wy7777LnFxcWi1Wp5//vlyy+Tn\n53Pjxg3zgWFmZiZt27atNNZbnT9/nieffNLi8pWpF6ek7nZ651bjx49n/vz5nD17FrjZ1a3odAqA\nl5cXWq2W1NRU87Rhw4axaNEi0tLSKCoq4oMPPmDYsGFotbc39datW83Luri4mLvgcPOi95o1azCZ\nTGzfvp19+/ZZvA134+Pjc0+3Cz7yyCN8++23JCUlATd/KNu2bavwCL1fv35cuHCBVatWYTAYKC0t\n5ejRo+YLwb+13rsZMWIEmzdvZufOnZhMJkpKSti7d69FR/oVfY/r1683L+vu7o5Wq63wOxwyZAhb\nt25lz549GAwGFi5ciJOTE2FhYRbFbTKZ0Ov1GAyGcv8HiIyMRKvVsnjxYvR6vbknFhUVBcDIkSNZ\ntGgROp0OnU7HokWLGD16dKXL9ujRo8I47OzsGDRoEH//+9/Jz8+nV69eAOae79y5c8338+t0Onbu\n3AlAfHw8y5cvZ8+ePSil0Ol05u/61/tZTEwMFy9eJCEhAaPRyNq1azl//jyxsbEVxnTlyhU2b95M\ncXEx9vb25t9IRXr16sXx48fNF+N/+OEH7OzsWLduHatWrWLVqlWsXbuWbt26sXLlSuDmb2zjxo3c\nuHGDixcv8t1335Wr89fxP/jggyxfvpzk5GT0ej3vv/8+Xbp0oXHjxrRs2ZKxY8cyffp0du/eTUlJ\nCSaTiYMHD1Z6t1VpaSmlpaU0atQIrVbLtm3b2LVrV7kySik+/vhjSktL2b9/P1u3bmXIkCF3rPNW\nOp2Oa9eu0aVLF4vK302tTxiW3Pr26zKVfe7fvz9PP/00r7zyChEREYwYMeKOt8U1aNCAadOmMX78\neCIjI0lKSiI+Pp6HHnqICRMmMGDAAJydnXn99dcrXD4lJYXJkyfTtWtXxo8fz2OPPUb37t0B+NOf\n/sTmzZvp3r07CQkJ9O/f/7628Vbx8fGcPXuWyMhIXnjhhbuW79SpE3PmzGH27NlERkYyaNCg2+44\n+YWrqytffvkla9eupU+fPvTp04f33nuv3F01ltZ7L9sUEBDAvHnzmD9/PtHR0cTGxvLll1+aDxYq\nW7ai7/Ho0aM8/PDDhIeH8/zzz/OnP/2JoKCg25Zt2bIl7777LnPmzCE6OpqtW7fy2WefYW9vf9f1\nws3TJp07d2b27NkcOHCALl26mB86dXBwYN68eaxYsYLIyEiWL1/OvHnzzHWPGzeO2NhYRowYwYgR\nI4iNjeWRRx6xaNmKDBs2jJ9++okhQ4aUS46///3vad68OY888ggRERFMmTKFlJQUADp37szcuXOZ\nO3cu3bp1Y9KkSebz9ZMmTWL9+vVERUXx9ttv4+npyWeffcbChQvp0aMHCxcuZP78+eZTRr9uK5PJ\nxL///W/69u1Ljx492LdvH2+++WaFsXt7e9OjRw82bdoE3Lw7asyYMfj7++Pt7W3+99hjj7F69WpM\nJhOTJ0/GwcGBXr16MXPmTIYPH16uzhdffJEZM2YQGRnJ+vXriY6O5uWXX+bFF1+kT58+pKWl8f77\n75vL//nPf2bixIm88847REVFERMTw8cff8yHH35oPhX36210dXXlT3/6Ey+//DKRkZGsXbv2tluD\nfX198fDwoE+fPsyYMYPZs2fTokWLO36Pt1q9ejWjRo3CwcHBovJ3o1H3cvh9j2bNmsXWrVvx9va+\n48WmxMRE3nnnHQwGA40aNWLx4sXWCkcIUYedO3eOP/7xjyxdutTWoVSZvXv3MmPGDLZu3XrPy+r1\nekaOHMlXX32Fl5dXlcRj1YSxf/9+XF1dmTFjRoUJo6CggHHjxvHll1/i7+9Pbm5ulW2YEELUdveT\nMKzBqqekIiIiKr06v3r1agYOHIi/vz+AJAshhKjBbHoNIyUlhWvXrjFx4kTGjBljvhglhBDi5s0L\nNaV3ATa+rdZoNHLixAn+7//+j+vXrzNu3Di6du1K8+bNbRmWEEKICtg0Yfj7+9OoUSOcnJxwcnIi\nIiKC5OTkuyaMXx6mE0IIUX2snjAqu6YeFxfHW2+9hdFoRK/Xk5SUxBNPPHHXOjUaDdnZBXctVx/4\n+rpJW/xM2qKMtEUZaYsyvr5udy9UCasmjFdffZXExETy8vLo168fL774IqWlpWg0GsaOHUtwcDC9\ne/dmxIgRaLVaHnnkkUqHghZCCGE7Vr2t1prkiOEmOXoqI21RRtqijLRFmfvtYdT6J72FEEJUD0kY\nQgghLCIJQwghhEUkYQghhLCIJAwhhBAWkYQhhBB1VFXfBCsJQwgh6qD07EJe+HAHe05kVVmdkjCE\nEKIOWrnjAsUlBlwbVM3Lk0AShhBC1GqLN5zi201nyp1+StUVcOB0Nq0au9OpZdW9NsKmgw8KIYT4\n7c6mX2PLoXQA/Bs5ExveBIBVOy8AMLJ3yyodqFUShhBC1FIb9qYC4Oig5ZtNZ2gR6I5Wo+HQmRyC\ng9zpWIW9C5BTUkIIUStdvnqdg6eyaR7gxgujQjEaFZ+uPMaSLWcBGNmnVZW/BkJ6GEIIUcMopZi3\n8hhXC0ro0MKLTi29aNXYHXu7smP8jfsuoYDBkc3o1MqbB3u2YPXuFHKu3aBNEw86NG9U5XFJwhBC\niBrmXEY+B05lA3A+I581u1Nwc3HgyWHt6RzsQ2FxKTuTMvF2dyIixBeAh3q35Gz6NU5evGqV3gVI\nwhBCiBpn19FMAJ4b2Qk7Ow3HLuSy40gmHy5NYlh0c+y0GvQGEwO6N8NOe7PXodVqeGlMZzKuFNEy\n0N0qcUnCEEIIG7mYVcD5jGv06xpk7hHoS43sPamjkZsT4W190Wo1dG3jS9/Ojfl05TESfroIgLOT\nPX06B5arz8nRzmrJAuSitxBC2IS+1Mi/Vhxl8cbTJJ7UmacfOpNDcYmRnp0C0GrLTis1D3Djz5O7\nE9725imouG5BODtV7zG/9DCEEMIGNuxNJefaDQCWbD5Ll2AfnJ3szaejenYKuG0Zlwb2PD+qE5cu\nF9LEt2G1xgvSwxBCiGqXm3+DhD0XcXdxYGD3puQV6lm9K4WrBSUcT8klOMidQG/XCpfVaDQ083cr\n1/uoLlZNGLNmzaJnz54MHz680nJJSUl06NCBjRs3WjMcIYSoEZZtO4e+1MSYmGBG922Fj0cDfth/\nieXbzqEU9AoNvHslNmDVhDF69GgWLlxYaRmTycR7771Hnz59rBmKEELUCGfTrrHnuI7mAW706hyI\no4Md4/u3wWhS7DqWhb2dlsgQP1uHWSGrJoyIiAjc3Su/Yr948WIGDRqEl1fVPsIuhBA1jUkpvtl0\nGoBH+7dB+/OdUWGtfegc7A1AeFsfXKpwhNmqZNNrGDqdjh9//JHx48fbMgwhhKgWe45ncSGzgMj2\nfrRp4mmertFomDCgLV2CvRkW3cJ2Ad6FTe+Smjt3Lq+99pr5/uOqfjuUEELUFCWlRr7bdh4Hey0P\n92t923wfT2defriLDSKznE0TxrFjx3jllVdQSnH16lW2b9+Ovb09cXFxd13W19etGiKsHaQtykhb\nlJG2KFMT2uLbH05xtaCEh+PaENLa19bh/CZWTxiV9Ro2bdpk/v/MmTOJjY21KFkAZGcX3HdsdYGv\nr5u0xc+kLcpIW5SpCW1xtaCEpZtO4+7iQL/OgTaL534Tp1UTxquvvkpiYiJ5eXn069ePF198kdLS\nUjQaDWPHjrXmqoUQosZYseM8+lIT4+PaVPvT2VXJqpG/9957Fpd95513rBiJEEJUv5JSI9sOpbMr\nKZMmvq706dzY1iHdl9qb6oQQwoYMRhMnUq4S0swTRwe7cvOKSwxsOZTOhr2pFFwvxdFBy4SB7Wzy\ndHZVkoQhhBC/wfJt51m/N5UALxeeHNae4CAPlFLsP5XNf388zbVCPc5OdjzYszkDIpri5uJo65Dv\nmyQMIYS4R5lXivhh/yWcnezR5V5n7lcH6N+tKZm5RRw7n4u9nZbhPVswKLJpjX0I77eQhCGEEPfo\n201nMZoUU4aG4ObiyJcJJ/lh/yUAOrb0YsLAtvg3crFxlFVPEoYQQtyDI2dzOHr+Cu2bNyK8rS8a\njYb/NyWSH/ZfIsDLhW7tfK3yetSaQBKGEEJYyGA08e2mM2g1Gsb3b2NODE6OdjzYs4Vtg6sG8j4M\nIYSwgMFoYvm28+iuFhPbNcgmLzCyNelhCCFEJZRSHDqTw9Kt59DlXsfD1ZGH+rS0dVg2IQlDCCF+\n5WpBCeczrnE+I58TF69yMasArUZDv65BPNS7JQ2d686dT/dCEoYQQvys1GDi281n2HIw3TxNA3Rt\n40N8v+A7vja1vpCEIYSol86k5eHsaE9jX1e0Gg05ecXMW3mMlKwCGvu4Et3Rn1aB7rQIdK/V4z9V\nJWkFIUS9s+NIBv9elwxAQ2cH2jTx4PSlPIpuGOjVKYAJg9rh9KvhPoQkDCFEPXMq9Sr/2XAK1wb2\ndGntw6nUqxw6k4O9nZbJQ0Lo0zmwzj5Hcb8kYQgh6g3d1et8svwoAM+PCiWkeSOUUuRcu4GTgx3u\nrrV/vCdrkoQhhKgXim6U8tHSJIpuGJg8JISQ5o2Am+/T9vV0tnF0tYM8uCeEqPOMJhOfrTxGVu51\nBkc2o2+X2v1eCluRhCGEqPOWbjnH8ZSrdA72Jr5fsK3DqbUkYQgh6rQf915k475LBHq78MyIjrX+\nJUa2JAlDCFGrXS0oYfPBNIpulN4270xaHv9aloRrA3teiu8sz1PcJ6u23qxZs9i6dSve3t6sXr36\ntvmrV6/m888/R6PR4OLiwl/+8hfatWtnzZCEEHVIid7I+0sOk55dxIrt5xkW3YK4bkHorhazds9F\nEk/o0Gg0TBvZqU6+n6K6aZRSylqV79+/H1dXV2bMmFFhwjh8+DDBwcG4ubmxfft2PvnkE5YsWWJR\n3dnZBVUdbq3k6+smbfEzaYsy9aEtlFIsWH2CxBM62jdvxMWsAq6XGGjo7EBh8c3eRhNfV6aM6EQL\n3/o9pMcvfH3d7mt5q/YwIiIiSE9Pv+P8sLCwcv/X6XTWDEcIUYf8eCCNxBM6goPceeWRLpSUGkn4\n6SJbDqUTHOTOsOgWdAn2xs/Pvc4nz+pSY07oLV26lL59+9o6DCFELXD6Uh5LNp/F3cWB50aGYm+n\nxd5OyyOxrXkktrWtw6uzakTC2LNnD8uXL+e///2vxcvcb9eqLpG2KCNtUaautsW5tDzmrTyGAv44\nOZK2rXzuukxdbYvqZvOEkZyczJ///Ge++OILPDw8LF5Oupg31Ydz1ZaStihTV9vibPo1PlhyhBsl\nBh4fEkKAu9Ndt7OutsVvUaOvYcDNC1N3kpGRwUsvvcTf//53mjVrZu1QhBC1WPLFq3y0LIlSg4mn\nhncgumOArUOqd6yaMF599VUSExPJy8ujX79+vPjii5SWlqLRaBg7dizz5s3j2rVr/L//9/9QSmFv\nb8+yZcusGZIQopZJyy7kx/1p7D6WiVLw7MhOdGvna+uw6iWr3lZrTdLFvEm622WkLcrU9rYwmkwk\nnb3CjwfSOHnxKgA+Hg2YNLgdnVp631Ndtb0tqlKNPyUlhBCWulpQwq6jmWw9nE5ufgkA7Zs3on+3\nJnRp7SPDetiYJAwhhM2YTIp9yZc5kZLL6Ut56K4WA+DkYEds1yBiuwbRxK+hjaMUv5CEIYSwCX2p\nkc9Xn+DA6WwAGjjaEdrKm87B3vTsFCDjPtVA8o0IIapdwXU9//zuKGfTrxHSzJNHHmhNMz83OeVU\nw0nCEEJUq8t5xXyw5Ai63Ov06ODPE0Pb42AvA2fXBpIwhBDVJj2niH98e4hrhXqG9mjO6JhWaDXS\nq6gtJGEIIapFqq6Af3x7mMLiUsY90JqBkfKwbm0jCUMIYTUmk6KguJSLWQUs+P44xSUGHh/cjpiw\nIFuHJn4DSRhCiCp3IiWX/2w4RU7eDUw/Pxus1WhkSI9aThKGEKJKHTiVzfzvjwHQqrE7Hg0d8XR1\nIrydL+2bN7JxdOJ+SMIQQlRqx5EMDp3JoW9YYzoHe1d6kXrHkQwWrU/G0d6OF8eE0qGFVzVGKqxN\nEoYQ4o5+EQoNAAAgAElEQVRKDSaWbj1HYXEph8/mEOjtQv9uTWjo4oi+1Ii+1Ej+9VKuFtzgyrUb\nHE+5imsDe155JIxWjd1tHb6oYpIwhBB3dOhMNoXFpUS298PeTkviCR2LN56+Y3n/Rs68MDqUIF8Z\nzqMukoQhhLijHUmZAIzo1ZLGPq6M7tuKg6ez0Wg0ONprcXDQ4ubiiJebE54NnWQ4jzpOvl0hRIVy\n8oo5cSGX1kEeNPZxBcDLvQH9I5raODJhK/I8vhCiQjuPZqKAPl0CbR2KqCEkYQghbmMyKXYezaSB\nox3dQ/xsHY6oISRhCCFuczwll9z8EiLb+9PAUc5ci5skYQghbrP9SAYAfbs0tnEkoiaxasKYNWsW\nPXv2ZPjw4Xcs89ZbbzFw4EAeeughTp48ac1whBB3kXmliM9WHePgqWyCfF1pGXh/74AWdYtV+5qj\nR49m4sSJzJgxo8L527ZtIzU1lY0bN3LkyBHefPNNlixZYs2QhKi3SvRGPlt1DEcHO8Ja+xAa7I1r\nA3uuXLtBSlYBh87ksOdEFkpBc383Jg8JQSNDj4tbWDVhREREkJ6efsf5mzZtYuTIkQB06dKFgoIC\ncnJy8PHxsWZYQtRLq3encOTcFQD2JV9Gq9Hg7GRH0Q2DuUwTX1ce6t2K8LY+kizEbWx6Nevy5csE\nBJSNXOnv749Op5OEIUQVy7xSxIa9qXi7N+D50Z04fiGXw2dzKLheSocWXrQIcKNloDttm3nKC43E\nHdk0Yaifhz2+laVHNb6+cm71F9IWZaQtyvzSFkopPvouCaNJMW1MZ7p3CqR7aP16H4XsF1XDpgnD\n39+frKws8+esrCz8/Cy75zs7u8BaYdUqvr5u0hY/k7Yoc2tb7D2p48iZHDoHe9PKz7XetZHsF2Xu\nN3Fa/bbainoRv4iLi2PlypUAHD58GHd3dzkdJUQVKiwu5dtNZ7C30/Jo/zZyXULcF6v2MF599VUS\nExPJy8ujX79+vPjii5SWlqLRaBg7diwxMTFs27aNAQMG4OzszDvvvGPNcISos0r0Rq6XGCg1mjAY\nTBy9mMeWfakcu3AFg1ExolcL/Bq52DpMUctpVGVdgBpMupg3SXe7TH1rC6UUpy/lseVQOgdOZWM0\n3f5TbuLrSlQHfwZFNsPern4+p1vf9ovK3O8pKXnmX4ha6PSlPBZvOEV6ThEAgd4uNPN3w95Og4Od\nlqaBHoQ0cSfQ29XGkYq6RBKGELXMoTPZfLryOCaTIrK9H7Fdg2jb1LPc9Qk5qhbWIAlDiFpk19FM\n/r02GXt7DS+N6UynVt62DknUI5IwhKihDEYTmw+kkX3tBiaT4nqJgcQTOlwb2PO7h7sQHORh6xBF\nPSMJQ4ga6rtt59iw91K5aY3cnHjlkS40kXdmCxuQhCFEDXToTDYb9l7C38uFaSM6Ym+vxU6rwdvd\nCQd7O1uHJ+qpuyaMS5cusWzZMhITE8nKysLJyYmQkBAGDRrEwIEDsbeXnCNEVcrJK2bhmpM42Gt5\nbmQnmvpJb0LUDJX+tf/zn//M8ePHGTx4ML///e/x8fGhpKSEc+fOsXPnThYsWMBf/vIXwsLCqite\nIeo0g9HEp6uOc73EwOQhIZIsRI1SacKIi4tj9uzZt01v164dQ4cOJS8vj0uXLlWwpBDiXhmMJhYm\nnORCZj7RHQPo0znQ1iEJUU6lCSMmJqbShT09PfH09KzSgISoj0oNRj5deZzDZ3MIDnJn4qC2Mu6T\nqHEsGivgr3/9KwUFBRgMBh599FHCwsJYtWqVtWMTol64oTfw4dIkDp/NoUOLRvx+bFcaOMq1QVHz\nWJQwdu/ejZubGzt37sTf358NGzbw5ZdfWjs2Ieq8VF0Bf/3qICcvXqVrGx9eju+Mk6PcBSVqpns6\njNm3bx8DBgzA399fustC3IdSg5Hvd6Wwbk8qJqWICWvMhIFtsdPWzwECRe1gUcLw9vbm9ddfZ9eu\nXUydOhWDwYDRaLR2bELUOfpSI3tO6Fi35yK6q8V4uzfg8cHtZIgPUStYlDDee+89vv/+e+Lj4/Hw\n8CAtLY0nnnjC2rEJUWcUFpeycV8qWw9lUFhcilajoX9EE0b3bSXXK0StYdGe6uXlxeTJk82fmzRp\nQpMmTawVkxA12rXCErRaDW4ujhaV1129zgdLjnD5ajGuDewZFt2c2K5BeLk3sHKkQlStShPGc889\nx7Rp0+jcufNt8woLC/nuu+9o0KABY8eOtVqAQtQkJXojbyzcyw29gZ6dAhkU2bTSd06cS7/GR8uS\nKCwuZUiPZozo1RInB7moLWqnShPGSy+9xHvvvUdKSgqdO3fG29ubkpISzp8/T3p6OuPGjWP8+PHV\nFasQNrc3WUdhcSmODlq2H8lgx5EMQoO96djSi3ZNPWni2xC9wYgut5jzGdf43+azlBpNTBrcjn5h\nQbYOX4j7UmnCCAkJ4fPPPyczM5O9e/ei0+lwcnJi8ODBdOvWDUdHy7rkQtQVO45kogFmPxlFalYB\n6xIvknTuCknnrgDgaK9FbzCZyzs6aHlpTGe6tPaxUcRCVB2LrmEEBgby0EMP/aYVbN++nblz56KU\nYsyYMUydOrXc/MzMTP7whz9QUFCAyWRi+vTpd33CXAhbSM8p4mz6NTq29MLP0xk/T2e6tfMl+9oN\nzlzK49SlPFIyC3B3dcDfy4WARi6EBnsT4OVi69CFqBIWJYwrV67wzjvvkJmZyddff01ycjKHDh26\n6+kok8nEnDlzWLRoEX5+fsTHxxMXF0dwcLC5zKeffsrQoUMZN24c586d4+mnn2bz5s33t1VCWMGO\nIxkA9O3S2DxNo9GYk0evUBn7SdRtFj0l9Prrr9OtWzfy8/MBaNWqFf/973/vulxSUhLNmzcnKCgI\nBwcHhg0bxqZNm8qV0Wg0FBYWApCfn4+/v/+9boMQVldqMLH7WBYNnR3o2kZOL4n6yaKEodPpGD9+\nPHZ2N+/ucHR0RGvBE6k6nY7AwLKjLn9/fy5fvlyuzAsvvMCqVauIiYlh2rRpvPHGG/cSvxDV4vDZ\nHAqLS+kVGoC9nTyNLeoni05J/folSfn5+Sil7rqcJWUSEhIYM2YMkydP5vDhw7z22mskJCTcdTlf\nX7e7lqkvpC3KWKst9iw/CsBD/drUmvauLXFWB2mLqmFRwhg4cCB//vOfKSoqYvny5fz3v/9lzJgx\nd10uICCAjIwM82edToefn1+5MsuWLWPhwoUAhIWFUVJSQm5uLl5eXpXWnZ1dYEnodZ6vr5u0xc+s\n1RapugIOn86mdRMPGmhrx74n+0UZaYsy95s4LepbP/XUU0RERNCxY0e2bdvGxIkTefzxx++6XGho\nKKmpqaSnp6PX60lISCAuLq5cmcaNG7N7924Azp07h16vv2uyEKI6KKXYcjCNtxcfQAEDIpraOiQh\nbEqjLDlvdB+2b9/O22+/jVKK+Ph4pk6dyscff0xoaCixsbGcO3eO119/nevXr6PVapkxYwbR0dF3\nrVeOGG6So6cyVdkW+UV6Fq1L5vDZHFwb2DN5SHu6tfOtkrqrg+wXZaQtytxvD8OihHHlyhW++uor\nUlNTMRgM5ukfffTRfa38fsgOcJP8GMpURVvkX9ezYW8qmw+kU1JqJKSZJ08P70gjN6cqirJ6yH5R\nRtqizP0mDIuuYTz33HN06NCB6Oho851SQtQl6dmF7EjKZNvhDEpKjXg0dCS+XzCxXYPQauXdL0KA\nhQmjuLiYN99809qxCFGtSkqNbD+cwe5jWVzU3TwC9WjoyJiYVsSENcbBXg6OhLiVRQmjS5cunDp1\ninbt2lk7HiGqRfLFqyxal8zlvGLstBq6BHvTMzSQsNbekiiEuAOLEsa4ceOYMGECAQEBODmVnctd\ntmyZ1QIToqqUGkwUXNejN5go0RvZejidbYcz0GhgUGRThkQ1x91VBtIU4m4sShivvfYa06ZNo0OH\nDnINQ9Qql/OK+etXB8gr1Jeb3sTXlSeGtqdloLuNIhOi9rEoYTg5OfHkk09aOxYhqpS+1Mi85UfJ\nK9TTtY0Pbi4OONrb4e/lQkxYYxniQ4h7ZFHC6NOnD9u3b6dv377WjkeIKqGUYvHGU6ReLqRvl8ZM\nHhJi65CEqPUsShhLlixhwYIFuLq64ujoiFIKjUbDTz/9ZO34hPhNth/JYNfRLJoHuPHYgDa2DkeI\nOsGihPHdd99ZOw4hqkROXjE/ndCxetcFXBvY8/yoTnLXkxBVxKKEERQk7yIWNVvSuRx+WHKE4+d/\nflWqg5ZnHuqIj4ezjSMTou6oNGG89tprvPvuu4wZMwaN5vanXeW2WmFrJqX4fucFvt+VAkBIM0+i\nOwbQrZ0fLg0sOh4SQlio0l/ULy87+sMf/lAtwQhxL4pLDCxMOMnB09n4eDTgjSd74OYodz4JYS2V\nJoxfXskaGRlZLcEIYYkSvZHEkzrWJ6aSlXudkGaePDuyE62CPGSQOSGsSPrsotbIKywh4aeL7D6W\nSXGJEY0G+ndrwiMPtJZnKoSoBpUmjNOnT1f4bgq5rVZUtwOnsvm/9ckUFpfi2dCRARFN6dulMV7u\nDWwdmhD1RqUJo0WLFixYsKC6YhHiNsUlBr758Qw7j2biYK/lsQFt5SltIWyk0oTh6Ogot9QKmzAY\nTWw7nMGa3SlcK9LT3N+NqSM6EOjtauvQhKi3Kk0YDg4O1RWHEMDN22R/OpbFqp0XyLl2AycHOx7q\n3ZJh0c2lVyGEjVWaMJYsWVJdcQhBenYh/7fhFGfTrmFvp2Vg96YM7SFDjwtRU1j9Lqnt27czd+5c\nlFKMGTOGqVOn3lZm7dq1/Otf/0Kr1dKuXTv+8Y9/WDssUYPkF+n5Yf8l1iemYjQpurXzZdwDbfD2\nkAvaQtQkVk0YJpOJOXPmsGjRIvz8/IiPjycuLo7g4GBzmYsXL/LFF1/wv//9j4YNG5Kbm2vNkISN\nGIwmVu9KQW8w4uxkj4uTPVfyb3Ai5SqXLhcC4O3uxGMD2hHWxsfG0QohKmLVhJGUlETz5s3NF86H\nDRvGpk2byiWMJUuW8Oijj9KwYUMAvLy8rBmSsJENe1NZvTvltun2dlo6tGhEp5be9OvamAaO8miQ\nEDWVVX+dOp2OwMBA82d/f3+OHj1arkxKSgoA48ePRynF888/T58+fawZlqhmV67dYPWuFNxdHHhh\ndGdKDEau3zDg2sCe1kEeODrIaLJC1AZWTRhKqbuWMRqNpKam8vXXX5ORkcFjjz1GQkKCucdxJ76+\nblUVZq1X09vi84ST6A0mnn+4C9Fdm1h1XTW9LaqTtEUZaYuqYdWEERAQQEZGhvmzTqfDz8+vXBl/\nf3+6du2KVqulSZMmtGzZkpSUFDp16lRp3TJm0E2+vm41ui2Onr/CT0czadPEg07NPK0aa01vi+ok\nbVFG2qLM/SZOq97YHhoaSmpqKunp6ej1ehISEoiLiytXpn///uzZsweA3NxcLl68SNOmTa0Zlqgm\npQYTX/9wGq1Gw4SB7SocIl8IUXtYtYdhZ2fHG2+8wZQpU1BKER8fT3BwMB9//DGhoaHExsbSp08f\ndu3axbBhw7Czs2PGjBl4eHhYMyxhRUopUnWFHDydzcHT2Vy+Wkz/iCY09av8FKMQoubTKEsuNNRA\n0sW8qSZ1t3OuFfOvFce4mHUzHns7LWGtvXliaHucnax/91NNagtbk7YoI21R5n5PSck9jKJKnEnL\n41/Lj5J/vZSubXyI7hhAp1ZecpusEHWI/JrFfTEpxc6kTBZvOIVS8NiAtjwQHiTXK4SogyRhiHtm\nMilOpl7l4Kmb1ymuFelxcbLn2VGd6NhCHrwUoq6ShCHuSeaVIr5Yc5ILmTdf39vQ2YHenQMZFt0c\n/0YuNo5OCGFNkjCERUxKselAGsu2nqPUYKJ7iB+xXYNo09QDO60MOy5EfSAJQ9xVzrVivkw4SXJq\nHg2dHXj6wQ5EhPjdfUEhRJ0iCUPckVKKHUmZfLvpDDf0RsJa+/D44HZ4NHSydWhCCBuQhFHPGYwm\ntBoNWm35u5oycopYsuUsSeeu4Oxkx5PD2tOzU4Dc/SREPSYJox7LL9Lz1n/2U1JqJCLEj6j2/rg2\nsGf17hT2nbyMAjq2aMQTQ9vj5S4vMxKivpOEUU+ZlOLzNSduvjfb0Y4tB9PZcjDdPL+ZX0OG92pJ\neFsf6VUIIQBJGPXW2p8ucvxCLqGtvHlxTCinUvNIPKEjr6iE2K5BhLWWRCGEKE8SRj10+lIeK3ac\np5GbE0892B57Oy0dW3rRsaU8dCeEuDNJGPWIUoqz6df4bNUxNGh4ZkRH3FwcbR2WEKKWkIRRD5Qa\njOw/lc0P+y6R8vNIsg/3C6ZtU08bRyaEqE0kYdRRpy/lcfB0NmfTr3ExqwCjSaEBurbxYWD3prRr\n1sjWIQohahlJGHXQ0fNX+HDJERRgp9XQzL8hIc0aEdM1CD9PZ1uHJ4SopSRh1DGX84pZ8P1x7Oy0\nPPtQRzq09MLJwc7WYQkh6gBJGHVIid7IJ98dpeiGgSeGhNC1ra+tQxJC1CEyzGgdoZRi0fpk0rIL\n6dc1iD5dGts6JCFEHWP1hLF9+3YGDx7MoEGDWLBgwR3LrV+/npCQEI4fP27tkOqU4hIDWw+n8+aX\n+0g8oSO4sTuP9m9j67CEEHWQVU9JmUwm5syZw6JFi/Dz8yM+Pp64uDiCg4PLlSsqKuKrr74iLCzM\nmuHUKSaTImHPRTbsTeX6DQN2Wg0RIX481r8N9nbScRRCVD2rJoykpCSaN29OUFAQAMOGDWPTpk23\nJYyPPvqIp59+mi+++MKa4dQZeYUlLPj+OMmpeXi6OdG/WxNiwoJo5CbDjgshrMeqh6I6nY7AwEDz\nZ39/fy5fvlyuzMmTJ8nKyiImJsaaodQZxy5c4S9f7iU5NY+w1j7Mm/EAI/u0kmQhhLA6q/YwlFJ3\nnT937lz+9re/WbzML3x93e4rttomJTOfxWtPsvdEFvZ2Gp5+qBPD+7RCo9HI8B63qG/7RWWkLcpI\nW1QNqyaMgIAAMjIyzJ91Oh1+fmWv9iwqKuLs2bNMnDgRpRQ5OTk899xzfPrpp3Ts2LHSurOzC6wW\nd02Sqitgw95U9hzXoYC2TTwY178NLQLcyckpxNfXrd60xd1IW5SRtigjbVHmfhOnVRNGaGgoqamp\npKen4+vrS0JCAu+//755fsOGDfnpp5/MnydOnMjMmTPp0KGDNcOq8fSlRvaevMzWw+mcz8gHbr6f\nYnRMMKGtvGTYcSGETVg1YdjZ2fHGG28wZcoUlFLEx8cTHBzMxx9/TGhoKLGxseXKazQai09J1UUm\nk2LXsUxWbD9PXqEeDdA52JuYsMZ0ae2DVhKFEMKGNKqW/oWua13Mkym5fLv5LJcuF+JoryUuogmx\nXYPw8ah87CfpbpeRtigjbVFG2qJMjT4lJe5OX2pkyZazbD6Yjgbo1SmAUX1byTu0hRA1jiQMG0q7\nXMj874+TnlNEkI8rTz7YnhYB7rYOSwghKiQJwwZMSrHpQBpLt5zDYDTxQHgQj8S2xlFGlRVC1GCS\nMKpZbv4Nvlx7khMpV2no7MATQzvStY2MKiuEqPkkYVQTfamR3ceyWLb1HNdLDHQO9uaJISF4NJQn\ntIUQtYMkDCvLyStm86F0dhzJoOiGAUcHLZMGtyOmS2N5nkIIUatIwrASk0mxLvEiK3dcwGhSuLk4\n8GDP5vQLC5I7oIQQtZIkDCu4cu0GX6w5walLeXg0dCQ+JpjI9n442MtFbSFE7SUJowoppdhzQsdX\nG09TXGIgvK0vjw9uJ4MDCiHqBEkYVSS/SM9/Npzi4OlsnBzsmDwkhD6dA+U6hRCizpCEcZ+UUuxL\nvsxXG09TWFxK26aeTBnWHj/Pyof0EEKI2kYSxn1Iu1zIf388TXJqHg72WsbFtaF/RBMZJFAIUSdJ\nwvgNCotLWbnjPFsOpaMUdAn2ZlxcG/y9XGwdmhBCWI0kjHtgMim2HU5n+fbzFN0wEODlwvj+bQht\n5W3r0IQQwuokYVjoVOpV/vvjGS5dLqSBox2PxLamf0QT7O2s+lp0IYSoMSRh3EVu/g2WbDnL3pOX\nAegdGsiYmFYypIcQot6RhHEHJXojG/elkrDnIvpSEy0D3Xh0QFuCG3vYOjQhhLAJSRi/YjCa2JmU\nyaqdF7hWpMfNxYHH+relV+dAuftJCFGvWT1hbN++nblz56KUYsyYMUydOrXc/EWLFrF06VLs7e3x\n8vJi7ty5BAYGWjus2xiMJhJP6Fjz00V0uddxdNDyYM8WDI5shksDyatCCGHVv4Qmk4k5c+awaNEi\n/Pz8iI+PJy4ujuDgYHOZDh06sHz5cpycnPjmm2/4+9//zgcffGDNsMopNZjYmZTBusRUcq7dwE6r\noV/XIEb0aoGnXKcQQggzqyaMpKQkmjdvTlBQEADDhg1j06ZN5RJGZGSk+f9hYWGsXr3amiGVk3ml\niM9WHefS5UIc7LXEhTdhcFQzvD1kNFkhhPg1qyYMnU5X7vSSv78/R48evWP5ZcuW0bdvX2uGZLbr\naCZfbTxNSamRPp0DGd1X7nwSQojKWDVhKKUsLrtq1SqOHz/O4sWLqzyOkxev8p/1yZQaTTg72qPR\naEjLLsTZyY5pD3Uksr1/la9TCCHqGqsmjICAADIyMsyfdTodfn5+t5XbvXs3CxYs4KuvvsLBwcGi\nun193Swqd+R0Nh8tS8JkMuHl4Uz+dT3Xbxjo2Mqb343rSoC3q2UbU4NZ2hb1gbRFGWmLMtIWVcOq\nCSM0NJTU1FTS09Px9fUlISGB999/v1yZEydO8Oabb7Jw4UIaNWpkcd3Z2QV3LXPswhX++d1RlFK8\nMDqUzsE+wM2ej0ajAZPJonpqMl9ft1q/DVVF2qKMtEUZaYsy95s4rZow7OzseOONN5gyZQpKKeLj\n4wkODubjjz8mNDSU2NhY3n33XYqLi3n55ZdRStG4cWPmzZt33+s+fCaHeSuPAfDimM7lxnuSd1QI\nIcS906h7udBQg2RnF1BSamRnUiYdWjQi8OdTS0op1iemsmzrOezttbw4JpROLevu4IBy9FRG2qKM\ntEUZaYsyNbqHYW3fbTvHj/vT0AARIX4MimzGjwcusee4jkZuTrwwOpSWge62DlMIIeqEWpswUnUF\nbDqQho9HA1ydHdiXfJl9yTcHCAxu7M7zo0PlwTshhKhCtTJhmEyKxRtPoRRMGtyOji28OHYhlw17\nU/Fv5MK4uDY42Muw40IIUZVqZcL4cV8q59LziQjxM1+fCG3lLS8yEkIIK6qVh+GL1pzAydGO8XFt\nbB2KEELUG7UyYRRc1zOyd0sauck1CiGEqC61MmE82Lslcd2a2DoMIYSoV2plwnhmVGd5l7YQQlQz\n+asrhBDCIpIwhBBCWEQShhBCCItIwhBCCGERSRhCCCEsIglDCCGERSRhCCGEsIgkDCGEEBaRhCGE\nEMIikjCEEEJYRBKGEEIIi1g9YWzfvp3BgwczaNAgFixYcNt8vV7PK6+8wsCBAxk7diwZGRnWDkkI\nIcRvYNWEYTKZmDNnDgsXLmTNmjUkJCRw7ty5cmWWLVuGh4cHGzdu5PHHH+fdd9+1ZkhCCCF+I6sm\njKSkJJo3b05QUBAODg4MGzaMTZs2lSuzadMmRo0aBcCgQYP46aefrBmSEEKI38iqCUOn0xEYGGj+\n7O/vz+XLl8uVuXz5MgEBAQDY2dnh7u5OXl6eNcMSQgjxG1g1YSil7rmMUgqNRmOtkIQQQvxG9tas\nPCAgoNxFbJ1Oh5+f321lsrKy8Pf3x2g0UlhYiIeHx13r9vV1q/J4aytpizLSFmWkLcpIW1QNq/Yw\nQkNDSU1NJT09Hb1eT0JCAnFxceXKxMbGsmLFCgDWr19Pjx49rBmSEEKI30ijLDlvdB+2b9/O22+/\njVKK+Ph4pk6dyscff0xoaCixsbHo9Xpee+01Tp48iaenJ++//z5Nmsj7uoUQoqaxesIQQghRN8iT\n3kIIISwiCUMIIYRFJGEIIYSwSK1LGHcbm6ouy8rKYtKkSQwdOpThw4fzn//8B4Br164xZcoUBg0a\nxJNPPklBQYGNI60eJpOJUaNGMW3aNADS0tJ45JFHGDRoENOnT8dgMNg4wupTUFDASy+9xJAhQxg2\nbBhHjhypl/vFokWLePDBBxk+fDivvvoqer2+Xu0Xs2bNomfPngwfPtw8rbL94K233mLgwIE89NBD\nnDx58q7116qEYcnYVHWZnZ0dM2fOZO3atXz77bd8/fXXnDt3jgULFhAdHc2GDRuIiopi/vz5tg61\nWvznP/8hODjY/Pkf//gHTzzxBBs2bMDNzY1ly5bZMLrq9fbbbxMTE8O6detYtWoVrVq1qnf7hU6n\nY/HixSxfvpzVq1djNBpJSEioV/vF6NGjWbhwYblpd9oPtm3bRmpqKhs3bmT27Nm8+eabd62/ViUM\nS8amqst8fX1p3749AK6urgQHB6PT6cqNxzVq1Ch+/PFHW4ZZLbKysti2bRsPP/ywedqePXsYNGgQ\ncLMdfvjhB1uFV60KCwvZv38/Y8aMAcDe3h43N7d6uV+YTCaKi4sxGAzcuHEDPz8/EhMT681+ERER\ngbu7e7lpv94PfvmbuWnTJkaOHAlAly5dKCgoICcnp9L6a1XCsGRsqvoiLS2N5ORkunTpwpUrV/Dx\n8QFuJpWrV6/aODrrmzt3LjNmzDAPI3P16lU8PDzQam/u0gEBAfVm30hLS6NRo0bMnDmTUaNG8cYb\nb1BcXFzv9gt/f3+eeOIJ+vXrR9++fXFzc6NDhw64u7vXy/3iF7m5ueX2g9zcXKD8OH5ws/10Ol2l\nddWqhCGPjNxUVFTESy+9xKxZs3B1da13Y29t3boVHx8f2rdvb94nlFK37R/1pV0MBgMnTpzg0Ucf\nZcWKFTg7O7NgwYJ6s/2/yM/PZ9OmTWzZsoUdO3ZQXFzM9u3bbytX39rlTir6e3q3trHqWFJVzZKx\nqcpAbb4AAAdwSURBVOo6g8HASy+9xEMPPUT//v0B8Pb2JicnBx8fH7Kzs/Hy8rJxlNZ18OBBNm/e\nzLZt2ygpKaGoqIi5c+dSUFCAyWRCq9WSlZVVb/aNgIAAAgICCA0NBWDgwIF8/vnn9W6/2L17N02b\nNsXT0xOA/v37c+jQIfLz8+vlfvGLO+0H/v7+ZGVlmctZ0ja1qodhydhUdd2sWbNo3bo1jz/+uHna\nAw88wPLlywFYsWJFnW+T6dOns3XrVjZt2sT7779PVFQU//jHP4iKimL9+vVA/WiHX/j4+BAYGMiF\nCxeAm9dyWrduXe/2i8aNG3PkyBFKSkpQSrFnzx7atGlT7/aLX/cc7rQfxMXFsXLlSgAOHz6Mu7u7\n+dTVndS6oUEqGpuqvjhw4AATJkygbdu2aDQaNBoNr7zyCp07d+Z3v/sdmZmZNG7cmI8++ui2C191\n1d69e/nyyy/57LPPuHTpEtOnTyc/P5/27dvz7rvv4uDgYOsQq0VycjJ/+tOfMBgMNG3alHfeeQej\n0Vjv9otPPvmEhIQE7O3t6dChA2+99RZZWVn1Zr949dVXSUxMJC8vDx8fH1588UX69+/Pyy+/XOF+\nMHv2bHbs2IGzszPvvPMOHTt2rLT+WpcwhBBC2EatOiUlhBDCdiRhCCGEsIgkDCGEEBaRhCGEEMIi\nkjCEEEJYRBKGEEIIi0jCEDXaAw88wNmzZ6tlXZ988km5oa9nzpzJ119/fd/1zpw5k+HDhzN9+vT7\nrqsyycnJrFu3zqrrEPWbJAwhfvbJJ59QWlpapXXm5OTw/9u7v5AmuziA49/ln7S8KOvWoghaI8KL\nihkJWon0R/Y8S2NYOL1IEFqE3gjRRZZEBcPyJqE/lDSIyBp2UV4IEVgGXeyiDKMVFnSRltTmaPr4\ney/Eh3KL9vYG7+vb73O182znnN/DYL+dHfY7fX199Pb2EgwGf+vYcz1//vyXE8b09PRvjkb9H2nC\nUPPS69evOXjwIDU1NRiGYZc+AHA6nXR1dVFdXU1FRQV9fX32c/fv32fnzp14vV66urpwOp0kEgna\n2tpwOBz4fD5M0yQWiwEwPDyM3++nsrKS1tbWH8Zz584dqqqq8Hg8BAIBPn78SDwex+/38/XrV0zT\n5OrVq9/1CYfDHDp0yG5blkVpaaldL+3ixYvs27cPr9dLU1MTY2NjAExOTnL69GmqqqowDINAIMD4\n+DidnZ08fvwY0zRpb28HZiojmKaJx+OhoaGBt2/fAjP/kDcMg5MnT+Lz+Xj48OE/eTvUn0KU+g8r\nLy+Xly9ffndtampKTNOUaDQqIiKxWEwqKyvt9tq1a+X69esiIvL06VMpLS0VEZHR0VHZvHmzjIyM\niIjIlStXxOl0ysTEhN0vkUjY87S2tkptba0kk0lJJpOye/duGRgYSIlxeHhYtm7dKqOjoyIi0tHR\nIUeOHBERkXfv3onb7U57b4lEQtxut3z69ElERPr7+8Xv94uISDgclmPHjtmvDYVC0tLSIiIinZ2d\nEggEZGpqSkTE7t/T0yOHDx+2+4yNjYnb7ZZXr16JiMjNmzelpqZGREQGBwfF5XJJJBJJG5tS6egK\nQ807b968IRqN0tzcjGEY7N+/n8nJye9OX9y1axcAxcXFfPjwgWQySSQSYf369RQVFQFQXV2dMrbM\nqZSzY8cOcnJyyMnJweVyMTIyktJncHCQsrIyli1bBoDP52NgYOCn95GXl8f27du5e/cuMFMYbvYQ\npP7+fh49eoRhGBiGQSgU4v3798BMefe6ujqysrIA7Oqsc0UiEdatW8fq1asB2Lt3L0NDQ0xMTACw\ncuVKNmzY8NM4lZo1r8qbKwUzH+qFhYXcvn077fMOh4OFCxcC2AfnWJaVkgzmttPJzc21H2dlZaU9\nD1pEUs4RmJ33ZwzD4NSpU+zZs4cnT55w9uxZe8ympia8Xm/a+TKRLq5v24sWLcpoHKVm6QpDzTur\nVq0iLy+PcDhsX4tGo8TjcSD1A3W2XVxczLNnz+zf8b/d9wAoKCjgy5cvfzuekpISHjx4YO8x3Lhx\ngy1btqTMn87GjRuJxWIEg0EqKirsRLdt2zZCoRCfP38GIJlM8uLFCwDKy8u5du2avUE/e5JeQUGB\nvfcye79DQ0N22fOenh5cLpcmCvXLdIWh/tMcDgf19fVkZ2fb35h7e3u5cOEC7e3tXL58GcuyWL58\nOR0dHXafuWPAzEEyx48fp7GxkaVLl1JWVkZ2djb5+fkANDQ0UFdXR35+Pt3d3RnHuGbNGpqbm6mv\nr2fBggUUFRXR1taWMv+PGIbB+fPnCYVC9jWPx8P4+DgHDhzA4XAwPT1NbW0tTqeTxsZGgsEghmGQ\nm5vLihUrOHfuHCUlJVy6dAnDMNi0aRNHjx7lzJkztLS0YFkWhYWF9gpGqV+h5c3VHyUej7N48WJg\n5hv3rVu3fst/LZT6E+gKQ/1Ruru7uXfvHpZlsWTJEk6cOPFvh6TUvKErDKWUUhnRTW+llFIZ0YSh\nlFIqI5owlFJKZUQThlJKqYxowlBKKZURTRhKKaUy8hf8CwfjbzhfpQAAAABJRU5ErkJggg==\n",
+ "text/plain": [
+ "\u003cmatplotlib.figure.Figure at 0x7f47b218dbd0\u003e"
+ ]
+ },
+ "metadata": {
+ "tags": []
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "plt.plot(ag_means)\n",
+ "plt.ylabel('Time(s)')\n",
+ "plt.xlabel('Length of vector')\n",
+ "_ = plt.title('Time to sum the elements of 1000 vectors (AutoGraph)')\n",
+ "_ = plt.ylim(ymin=0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "d7IAJ6Bwbk9t"
+ },
+ "source": [
+ "## Eager"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {
+ "autoexec": {
+ "startup": false,
+ "wait_interval": 0
+ }
+ },
+ "colab_type": "code",
+ "id": "XMu5-12yoOzY"
+ },
+ "outputs": [],
+ "source": [
+ "from tensorflow.python.eager import context"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {
+ "autoexec": {
+ "startup": false,
+ "wait_interval": 0
+ }
+ },
+ "colab_type": "code",
+ "id": "_vt9MzpyjQ4T"
+ },
+ "outputs": [],
+ "source": [
+ "# Sum written using for loop and run with tf.eager\n",
+ "def sum_all(elements):\n",
+ " sum_ = 0.0\n",
+ " length = elements.shape[0]\n",
+ " for i in tf.range(length): \n",
+ " sum_ += elements[i][0]\n",
+ " return sum_\n",
+ "\n",
+ "eager_means = []\n",
+ "for num in range(max_elements):\n",
+ " with context.eager_mode():\n",
+ " durations = []\n",
+ " for i in range(trials + burn_ins):\n",
+ " \n",
+ " start = time.time()\n",
+ " for _ in range(batches):\n",
+ " run_trial(num)\n",
+ " \n",
+ " if i \u003c burn_ins:\n",
+ " continue\n",
+ " \n",
+ " duration = time.time() - start\n",
+ " durations.append(duration)\n",
+ " eager_means.append(np.mean(durations))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {
+ "autoexec": {
+ "startup": false,
+ "wait_interval": 0
+ },
+ "height": 301
+ },
+ "colab_type": "code",
+ "executionInfo": {
+ "elapsed": 422,
+ "status": "ok",
+ "timestamp": 1532460024499,
+ "user": {
+ "displayName": "",
+ "photoUrl": "",
+ "userId": ""
+ },
+ "user_tz": 240
+ },
+ "id": "5gHVdMlD-A-T",
+ "outputId": "3b581cb7-7ef9-489c-92f1-3e52c0c2dc8a"
+ },
+ "outputs": [
+ {
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYkAAAEcCAYAAAAydkhNAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3XlclNX+wPHPsC+CILviCiruhAiiqaAimqnglpZLWpkt\ntmh50+rXvZZ2q9veq5ulZYtlaZp7mQuaorjklkoqiwgKsojsy8yc3x9cBxFUVGBYvu+/mGfmPM93\nzjzMd85zznOORimlEEIIISphYuwAhBBC1F2SJIQQQtyQJAkhhBA3JElCCCHEDUmSEEIIcUOSJIQQ\nQtyQJAlg8eLFvPrqq8YOo14ZOHAge/furfHjzJs3jw8//LDGj1NXxMfHExERQc+ePfnuu++MHU6D\ndvbsWcaMGWPsMCo1btw4YmNjjR0G0EiSxD333IOfnx9+fn506tSJHj16GLZt2LCBxx9/nNdff73G\n40hOTsbHxwe9Xl/jx6pOje2L+lZq8nNcsmQJgYGBHDp0iEmTJlV4fvPmzUyYMAFfX1+mTJlS4flT\np04xevRofH19GTNmDDExMeWef+eddwgMDKR379688847t1W2pk2ePJlVq1bV2vE++ugjHn30UcPj\ngQMH0qNHD/z8/AzfD2+88UatxXOtRx55pM78zzWKJHH48GH+/PNP/vzzT5o3b87ixYsN2+6///5a\ni0MphUajQe5frN9q8nO8cOEC3t7eN3zewcGBhx9+mBkzZlR4rqSkhKeeeorw8HAOHDhAeHg4Tz75\nJFqtFoAVK1awfft21q9fz7p164iMjOTHH3+sUtn64HY+j7S0NKKjoxk0aFC57YsXL+bPP/80fD+8\n8sor1R3mTel0OqA0YUVHR5Oenl6rx69Mo0gS11JKVTiZPvnkE1588UWg7Ffi6tWrCQ4OJjAwkBUr\nVnD8+HFGjhxJQEBAhVbHqlWruO+++wgMDOTRRx/lwoULlR578uTJAPj7++Pn58fRo0dRSvHpp58y\ncOBA+vbty0svvURubm6l5S9fvszMmTPp1asXgYGB5X5p+vj4cP78ecPja3/979+/nwEDBrBkyRL6\n9OlDv3792Lp1Kzt37iQsLIzAwEAWL15c6TF/+ukn1q9fz5IlS/Dz8+OJJ54wPHfq1ClGjhxJr169\nmD17NsXFxYbnduzYQXh4OL169WLixIn8/fffle4fIDY2lunTpxMYGMiwYcPYvHnzDV97s/0OHDiQ\npUuXMnLkSO655x5eeeUVMjIyeOyxx/Dz82P69Onk5OQYXn/kyBEmTJhAr169CA8PZ//+/YbnJk+e\nzIcffsjEiRPx8/PjkUceISsry/AclP8cExMTmTx5Mv7+/gQFBTF79uwbvodt27Zx//33ExAQwJQp\nU4iLiwNg6tSpREdHs2DBAvz8/Dh37lyFskFBQQwdOhQXF5cKz+3fvx+dTseUKVMwNzdn8uTJKKXY\nt28fAL/88gvTp0/H1dUVV1dXpk2bxpo1awCIjo6+adlrbdq0qcJlmmXLlvHkk08CUFxczFtvvUVI\nSAj33nsv//znP8udG1u3biU8PJyePXsyZMgQdu/ezfvvv8+hQ4d4/fXXy/2C//PPPxk7diy9evVi\n3LhxHD58uNxn9P777zNx4kR8fX1JSkpi9erVDB48GD8/PwYPHsyGDRsq/Qz27NlDly5dsLCwKLf9\nRonm/PnzTJ06lcDAQIKCgnjhhRfK/Z+eOHHCcJnw2Wef5fnnny/XErjVefvFF18Yzlu9Xo+FhQVd\nunRh9+7dlcZTq1QjExISoqKiospt+/jjj9WLL76olFIqKSlJdezYUb322muqqKhI7dmzR3Xr1k09\n9dRTKjMzU6WkpKigoCB14MABpZRSv//+uxoyZIiKi4tTOp1O/fe//1UPPPBApcdOSkpSPj4+Sq/X\nG7atXLlSDRkyRCUlJan8/Hz19NNPG2K53rvvvqtee+01pdPplFarVQcPHjQ85+PjoxITEw2PX3rp\nJfXBBx8opZSKjo5WnTt3Vp9++qnSarXqp59+Ur1791Zz5sxR+fn56syZM6pbt27q/PnzlR732n1d\nW4/jxo1TaWlp6sqVK2rYsGFqxYoVSiml/vrrLxUUFKSOHTum9Hq9WrNmjQoJCVHFxcUV9p2fn68G\nDBig1qxZo/R6vTp58qQKDAxUZ8+erXDsW+03JCREPfDAAyojI0OlpqaqoKAgFRERoU6dOqWKi4vV\nlClT1CeffKKUUiolJUUFBASoXbt2KaWUioqKUgEBASozM1MppdSkSZNUaGioOnfunCoqKlKTJk1S\n77777g0/x9mzZ6vPPvtMKaVUUVGROnToUKV1GRcXp3x9fVVUVJTSarXqiy++UKGhoaqkpMRw3JUr\nV1Za9lo//fSTmjx5crltX331lXrsscfKbXv88cfVV199pZRSqmfPnuro0aOG544fP678/PyqVPZa\nBQUFys/PT507d86wbcyYMWrTpk1KKaXeeOMN9cQTT6js7GyVl5enZs6cqd577z2llFJHjx5VPXv2\nNPwPpqamqri4uErfe1ZWlurVq5dat26d0ul0asOGDapXr14qKyvL8PqQkBB19uxZpdPpVE5OjvLz\n81MJCQlKKaXS0tIM59H13nrrLbVgwYJy2yr7brjq3LlzKioqSpWUlKjMzEw1adIktWjRIqWUUsXF\nxSokJER9++23SqvVqi1btqguXbrc1nkbHh6uUlJSVFFRkeGYr7/+uvr3v/9daTy1qdG1JKpCo9Hw\n1FNPYWFhQZ8+fbC2tmb48OE4Ojri5uaGv78/J0+eBODHH39kxowZtG3bFhMTE2bMmEFMTAwXL168\n4f7VNb9WNmzYwMMPP0yLFi2wtrZm9uzZbNq0qdLr3WZmZqSlpZGUlISpqSk9e/asdJ+VMTc3Z+bM\nmZiamnLfffdx+fJlpk6dirW1Nd7e3nh7e9/0135lpkyZgrOzM/b29oSEhHDq1CkAVq5cyYQJE+jW\nrRsajYbw8HAsLCw4evRohX3s2LEDT09PwsPD0Wg0dOrUiSFDhvDrr79WeG1V9jtp0iSaNWuGq6sr\n/v7+9OjRAx8fH8zNzQkNDTXEuG7dOoKDg+nXrx9Q+gu9a9eu7Ny507Cv0aNH06pVKywsLBg2bJih\n7FXX1rmZmRnJycmkpqZiYWGBn59fpXW2efNmgoODCQoKwtTUlEceeYTCwsJyv5DvVH5+PnZ2duW2\nNWnSxPCL9/rn7ezsyM/Pr1LZa1lZWTFo0CDDr/SEhATi4+MNl25WrVrFvHnzsLOzw8bGhhkzZhhe\nu2rVKsaOHUtQUBAArq6utG3bttL3ExkZSZs2bRgxYgQmJiYMHz6cdu3asWPHDsNrIiIi8PLywsTE\nBFNTU0xNTTl9+jRFRUU4Ozvj5eVV6b5zcnKwtbWtsP2pp54iICCAXr16ERAQwMqVKwFo1aoVQUFB\nmJmZ4ejoyNSpUzlw4ABQ2iLV6XRMmjQJU1NTQkND6d69u2GfVTlvp0yZgpubW7mWja2tLdnZ2ZXG\nX5vMjB1AXeXk5GT428rKCmdnZ8NjS0tLwz/XhQsXWLhwIW+99RZQdr06NTUVDw+PWx7n0qVLNG/e\n3PC4RYsWaLVa0tPTcXV1LffaRx99lI8//pjp06ej0WgYN25cpdemK+Pg4IBGozG8n8re49X3VFXX\nlre2tiYtLQ0orZO1a9caRucopdBqtVy6dKnCPi5cuMCRI0cICAgwvFan0xEeHl7pa2+132tjsrS0\nrPD42s9t8+bNhi+cq/u6+uUFlPvMra2tb1o/c+fO5YMPPmDs2LGGfoPKRs5c/3lrNBo8PDxITU29\n4b6rysbGpsKXem5uLk2aNKn0+dzcXGxsbKpU9nrDhw/n7bff5sknn2TDhg0MHjwYCwsLMjMzKSgo\nKPfe9Xq9IaGmpKQwYMCAKr2f6+sKoHnz5uXqyt3d3fC3tbU177//PkuXLmX+/Pn07NmTuXPn0q5d\nuwr7tre3Jy8vr8L2Tz/9lN69e1fYnpmZyRtvvMHBgwfJz89Hp9Ph4OAAlPZvuLm5lXv9tf/7VTlv\nr30fV+Xl5WFvb19he22TJHGX3N3deeKJJ6rUAX71S/parq6u5fowkpOTMTMzK/cFdZWNjQ3/+Mc/\n+Mc//kFsbCyTJ0+me/fu9O7dG2trawoKCgyvTUtLq/TEqw3u7u7MnDmTxx9//Jav9fDwIDAwkKVL\nl1brfqty3PDwcBYsWHDbZSv7HJ2cnAx9VYcOHWLatGkEBATQsmXLcq9zdXXlzJkz5bZdvHixWj6r\n9u3bs2zZsnLbTp8+behD8fb2JiYmhm7dugGlfUrt27e/adnKRlgB3HvvvcybN4+YmBg2btzI/Pnz\nAXB0dMTa2poNGzZU+JEDpZ/htX1n17q+Xl1dXdmyZUu5bRcuXKB///43LNO3b1/69u1LcXEx77//\nPq+++irLly+vcKyOHTuydu3aCttv1CJ/99130Wg0bNiwAXt7e7Zu3WroN3FxcamQ5C9evEirVq0M\n7/lOztu4uDhGjhx5W2VqglxuqsStLt1ca+LEiSxevJizZ88Cpc3Yyi6VADRr1gwTExMSExMN24YP\nH86yZctISkoiLy+P999/n+HDh2NiUvGjiYyMNJS1sbExNK+htON6w4YN6PV6du3aZWgKVwdnZ+cb\n/mNXZvz48axYsYJjx44BpZcydu7cWekv8eDgYOLj41m7di1arZaSkhKOHz9u6My90/3eysiRI9m+\nfTu7d+9Gr9dTVFTE/v37q/SLvrLP8ddffzWUtbe3x8TEpNLPcNiwYURGRrJv3z60Wi1Lly7F0tIS\nX1/fKsWt1+spLi5Gq9WW+xsgICAAExMTvv32W4qLiw2/XAMDAwEIDw9n2bJlpKamkpqayrJlyxg9\nevRNy1b2qxrA1NSUsLAw3n77bbKzs+nbty+AoYW7aNEiMjMzAUhNTTV0wI4dO5bVq1ezb98+lFKk\npqYaPuvrz7MBAwZw7tw5Nm7ciE6nY9OmTcTFxRESElJpTBkZGWzfvp2CggLMzMwM/yOV6du3LydO\nnCjXoX4zeXl52Nra0qRJE1JTU8v9qPH19cXU1JTly5ej0+nYunWr4RyFOztvi4uLOXHihKFejanR\nJYnKfgXe6jU3ezx48GAee+wxnn/+efz9/Rk5ciR//PFHpfu1srJi5syZTJw4kYCAAI4dO8bYsWMZ\nNWoUkyZNIjQ0FGtr6xsOu0tISODhhx/mnnvuYeLEiTz00EP06tULgJdffpnt27fTq1cvNm7cyODB\ng+/qPV5r7NixnD17loCAAJ5++ulbvr5r1668/vrrLFiwgICAAMLCwgyjaK5na2vLl19+yaZNm+jX\nrx/9+vXj3XffrfSf91b7vZ335O7uzqeffsrixYsJCgoiJCSEL7/80vAD4WZlK/scjx8/zrhx4/Dz\n8+Opp57i5ZdfpkWLFhXKtm3blnfeeYfXX3+doKAgIiMj+eyzzzAzM7vlcQHWrl1L9+7dWbBgAYcO\nHaJHjx6GG0HNzc359NNPWbNmDQEBAaxevZpPP/3UsO8JEyYQEhLCyJEjGTlyJCEhIYwfP75KZSsz\nfPhw9u7dy7Bhw8olxBdeeIHWrVszfvx4/P39mT59OgkJCQB0796dRYsWsWjRInr27MmUKVMM/XdT\npkzh119/JTAwkIULF+Lg4MBnn33G0qVL6d27N0uXLmXx4sU0bdq00rrS6/V89dVX9O/fn969e3Pg\nwAFee+21SmN3cnKid+/ebN26tdz2J554wnBPlZ+fH7NmzQLg6aef5q+//sLf35+ZM2cSFhZmKGNu\nbs7HH3/MypUr6dWrFxs2bGDgwIGG/oXbPW+hdARcYGBgpaPYaptG3c7P5ts0f/58IiMjcXJyYv36\n9QDExMTw2muvUVRUhJmZGa+99pqh+SuEELUlNjaWl156ydA5XZ3Gjx/PxIkTiYiIuKPyDzzwAAsX\nLrzpPTO1pUaTxMGDB7G1tWXu3LmGJPHII48wbdo07r33Xnbu3MmSJUv49ttvayoEIYSocQcOHKBt\n27Y4Ojqybt06/vWvf7F169ZK+xbrmxrtuPb39yc5ObncNo1GY7ihKScnp8KoACGEqG/i4+N57rnn\nyM/Pp1WrVnz00UcNIkFADbckoHS0zsyZMw0tidjYWB599FHDnc8rVqyo0lBRIYQQta/WO65/+OEH\nXn75ZSIjI5k3b55h6JwQQoi6p9aTxC+//GIYeTN06NByQ8VupoYbPEIIUe9l5xVz/Gx6tX5f1vjN\ndNcH6+bmxv79+wkICGDv3r20adOmSvvRaDSkpeXc+oWNgIuLndTF/0hdlJG6KNPY6iL9SgFb9p9n\n17ELFJfo+ee0XrRyK51mxcXF7halb65Gk8ScOXOIjo4mKyuL4OBgZs2axeuvv84bb7yBXq/H0tKy\nVtZxEEKIhijjSiG/7I5j71+p6JWimb0lw4Jb09K18ulU7kSNd1xXp8b0y+BmGtuvpJuRuigjdVGm\noddFXmEJG/eeY+vBJLQ6Pc2dbRkW2IrAzm6YmZbvRajTLQkhhBDV61RCJv9de4LcghKa2VsS0a8d\nQV3cMTG59WwSd0KShBBC1BORR5JZvuU0AGODvRjc0xML88rnp6oukiSEEKKO0+n1/LQ9lt8PnqeJ\ntTlPj+5Gh5YOtXJsSRJCCFFHXczIY/fxi+z9K4Ws3GI8nGx4dlwPXB2say0GSRJCCFHHFBRp+WL9\nSY6cTQfA2tKMgX4tGN3fCxur2v3aliQhhBB1SGZ2IR+sPEZSWi7enk0Z5OfJPe2da7zv4UYkSQgh\nRB1x/lIuH6w8yuWcIkL8WvDQ4A41NmqpqiRJCCGEESmlOJt8hT3HU4g+mUpRiY7xId6EBbSs0iJp\nNU2ShBBCGMmhvy+xMjKWS5dL16d3tLNk+vBO9PKpuD64sUiSEEKIWqbXK9b8EcfGvecwNzMhqIsb\nfbp50KmVo9EvL11PkoQQQtSi/MISFq87yfG4DFwdrZk1uhstXKpvrqXqJklCCCFqScaVQt798Qgp\nmfl0bdeMx0d2wdbK3Nhh3ZQkCSGEqAUX0vN498cjXM4pYkivlowP8a5zl5YqI0lCCCFqWNyFbD5Y\neZTcghLGhXgxLLC1sUOqMkkSQghRA5RSJKTksPvYRfb8dZESrZ5pw3zo16O5sUO7LZIkhBCimh2L\nzWBV5FmS0vIAcGhiweQRHbmng4uRI7t9NZok5s+fT2RkJE5OTqxfv96w/dtvv2X58uWYm5szYMAA\nXnjhhZoMQwghas3Wg+f5YdsZTDQaenZ0oV93D7q0bYapicmtC9dBNZokRo8ezeTJk5k7d65hW3R0\nNDt27GDDhg2YmZmRmZlZkyEIIUSt0OsVK7afYevBJOxtLXh2bHfaetgbO6y7VqNJwt/fn+Tk5HLb\nfvjhBx577DHMzEoP3axZs5oMQQghaoRerzgWm0Fyei5pWYWcS83hXEoOzZ1teW5sd5xrcTrvmlTr\nfRIJCQkcPHiQ999/H0tLS+bOnUu3bt1qOwwhhLhjWp2epRtPEX0ytdz27l5OzBjRGZs6fu/D7aj1\nJKHT6cjOzuann37i2LFjPPfcc2zbtq1KZe92Qe+GROqijNRFGamLMjVVF0UlOv799QEOnkqlU5tm\njB3UHrdmNrg52mBl2fDGAtX6O3J3d2fIkCEAdO/eHRMTEy5fvoyjo+Mty6al5dR0ePWCi4ud1MX/\nSF2UkbooU1N1UVCk5eOfjxGTmEWXts14OqIblhal6zzkZBdQF2v/bpNljXe3K6XKPR48eDB79+4F\nID4+Hq1WW6UEIYQQxhR/MZvXvz5ITGIWPTu68MyY7oYE0ZDVaEtizpw5REdHk5WVRXBwMLNmzWLM\nmDHMmzePESNGYG5uzltvvVWTIQghxF3R6fVs3HuO9XsS0OkVQ3q1ZFyIV70d0nq7NOr6n/p1mDSl\nS8llhTJSF2WkLspUV12kXylg8boTxCZn42hnySPDO9G5Tf0akXm3l5saXi+LEEJUgyNn01m64SR5\nhVoCOrkyOaxjnZ+xtSZIkhBCiGsUlehY+0c8v+5PxMzUhKlDO9K/R/M6sZSoMUiSEEIIILeghO2H\nkth6KIncghLcmtnwxKgutHJr3MOKJUkIIRo1vV6xYW8Cm/ado7hEj62VGff3acOwwFZYN8D7Hm6X\n1IAQotG6klfM5+tOcOrcZRyaWDCmf2v69fDAykK+Gq+SmhBCNEp/J17ms7UnuJJXjK+3M4/c36lR\ndkzfiiQJIUSjUqLV88vuOH6NTkSDhnEhXgwNaNVoO6ZvRZKEEKLRSEzNYcmGkySl5eHc1IrHRnSm\nvaeDscOq0yRJCCEavBKtno17E9i49xw6vWKAb3PGh3hLx3QVSA0JIRq00+ez+PrXGC5m5ONoZ8nU\noT5093Iydlj1hiQJIUSDlH6lgLV/xLPnrxQ0wCA/T0YPaCeth9sktSWEaFCu5Baxenc8m6NKJ+Tz\ndLFlylAfvFs0NXZo9ZIkCSFEvafT6zkRn8nu4ykcOZOGVqdwcbAi/N52BHZ2w8RERi7dKUkSQoh6\nq6hYx/Y/k9hy8DxXcosBaO5sS/gAL3zbNcPMtHFM512TJEkIIeqdohIdO/5MZnP0OXLyS7C2NCPE\nrwX3dvOgjbsdrq72Mm16NZEkIYSoV1Iz8/lw1TFSMvOxtjRlZN82DOnVEhu5W7pG1GhbbP78+fTp\n04cRI0ZUeG7p0qX4+PiQlZVVkyEIIRqQUwmZvPHNQVIy8xnU05O3ZvYhvF87SRA1qEaTxOjRo1m6\ndGmF7SkpKURFRdG8efOaPLwQooEo0erYevA87/10lMJiHdPu8+Gh0A40sZbkUNNq9HKTv78/ycnJ\nFbYvWrSIuXPn8sQTT9Tk4YUQ9ZhSiuNxGUSfvMSRs2kUFOloYm3O06O70aGlTKVRW2q9T2L79u14\neHjQsWPH2j60EKIeWRUZy+boRACc7K0Y4NuCQX6eODW1MnJkjUutJonCwkI+++wzvvzyS8M2pVSV\ny9/tgt4NidRFGamLMg2lLk7EZfDr/kQ8nG2Z/aAfHVs53vYsrQ2lLoytVpNEYmIiycnJjBo1CqUU\nqampjBkzhpUrV+LkdOu5VGRIWykXFzupi/+RuijTUOqiqFjHe98fAgXThvrgZGNOenrube2jodRF\ndbjbZFnjSeLalkKHDh3Ys2eP4fHAgQNZs2YNTZvK7fJCiFKrdsZy6XIBQwNa4e0p3w3GVqOjm+bM\nmcOECROIj48nODiYn3/+udzzGo3mti43CSEatphzl9l2KAkPJxvC+7U1djiCGm5JvPvuuzd9ftu2\nbTV5eCFEPXL4TBpfbjyFRgOPDO+MhbmpsUMSyB3XQggj0+r0rIqMZcuB85ibmTD9vk60a25v7LDE\n/0iSEEIYTWJqDl//+jfxF7Nxb2bDE+FdaenaxNhhiWtIkhBC1LrcghLW7Ioj8kgySkFQFzcmh3XE\nykK+kuoa+USEELXm6uytG/cmkFeoxcPJhomD29O1rSwnWldJkhBC1LgSrY7IIxfYtPccV/KKsbY0\nY8JAbwb29JQ1H+o4SRJCiBqhV4qzSVfYdzKVA6dSySvUYmlhyv19WhMW0Apbmbm1XpAkIYSodhcz\n8vho1TFSLxcA0NTWgvt6tyYsoCV2NhZGjk7cDkkSQohqdTmniPd+PEJGdhG9u7jRt6sHnVo7yjrT\n9ZQkCSFEtckvLOG9n0oTRET/dozo08bYIYm7JD1GQohqUVSi46NVx0hOy2OQnyf3B7U2dkiiGkhL\nQghxx/ILtRw9m87hs+n8FZdBYbGOXj6uTBzc/ran9hZ1kyQJIcQdOZt8hU9+PkZ2fgkALg5WDOrp\nyci+baX/oQGRJCGEuG37TqTw5aYY9HrF/X1aE9jJjebOttJ6aIAkSQghqkwpxdrd8azbk4C1pSlP\nhHeTu6UbOEkSQogqUUqx/PfTbP8zGeemVjw7rgctnG2NHZaoYZIkhBC3pFeK5VtOs+NwMp4uTXhh\ngi/2tnJTXGNQo0li/vz5REZG4uTkxPr16wF4++232bFjBxYWFrRq1Yo333yTJk1kamAh6iq9Uny3\n5TSRh5Np6VqaIOSu6cZDo2pw/dCDBw9ia2vL3LlzDUkiKiqK3r17Y2Jiwn/+8x80Gg1z5syp0v5k\nYfNSssh7GamLMtVVF4XFWtbtTiD2whWy84rJzi+moEhHK9cmvDDxHppY1/05l+S8KOPiYndX5Wu0\nJeHv709ycnK5bX369DH87evry2+//VaTIQghbkNs8hW+WH+SS1kFaDRgZ22Ok70VLVya8FBoh3qR\nIET1MmqfxKpVqxg+fLgxQxBCAHq9Yn1UAuv3JKCUYlhgK8L7tcPcTCZlaOyMliT++9//Ym5uzogR\nI6pc5m6bTQ2J1EUZqYsyd1IX+YUlvPPdIQ6eSsXZwZrZE/3o5u1cA9HVLjkvqodRksSaNWvYuXMn\n33zzzW2Vk2uMpeR6axmpizJ3UheZ2YV8uOoY5y/l0qVtM2aO6oKtlXm9r1M5L8rU6T4JKB1bfa1d\nu3axZMkSvvvuOywsZISEEMZyLiWHD1cdJSu3mGDf5jwY2kFWiRMV1GiSmDNnDtHR0WRlZREcHMys\nWbNYvHgxJSUlTJ8+HYAePXrwz3/+sybDEEJc5+/Ey3y46hhFxTrGh3gTFtBSptQQlarRIbDVTZqP\npaQpXUbqokxV6+LI2XT++8tf6PWKx0Z0JqCTWy1EV7vkvChT5y83CSGMK7eghKycIrLzi0lMzWVV\nZCxmphqeGdudbu1k3iVxc5IkhGjA/jh6gW9++xudvuyCgY2lGc+O6057TwcjRibqC0kSQjRQx2Iz\n+PrXv7GxMqNXJ1fsbSywszGnWzsnXBysjR2eqCckSQjRACWkZPPfX/7C1FTDs2O749WiqbFDEvWU\njHcTooFJSsvlg5XHKC7RMWNEF0kQ4q5IS0KIBuDvxMus3h3PgRMppF4uAOCh0A707Ohi5MhEfSdJ\nQoh6rKhEx4/bzxJ5uHQiTUsLU+5p70xAJzcCOze8oa2i9kmSEKKeSrqUy2frTnAhPQ9PF1seH90d\nN3tLuWtXb7olAAAgAElEQVRaVCtJEkLUMyVaPb/tT2TdngS0Oj2D/DwZF+JFi+YOcgOZqHaSJISo\nR04mZPLdltOkZOZjb2PO1GFduKe99DuImiNJQoh6oLBYy/LfT7PneAoaYKBfC0b3b4eNlSwCJGqW\nJAkh6rjE1Bz++8tfpF4uoLWbHVOHdaSNu72xwxKNhCQJIeoopRTb/0zmx+1n0OoUQwNaMXpAO+mY\nFrVKkoQQdZBSip93xrFp3zmaWJvz6P2d6O5V/1eLE/WPJAkh6hi9UqzYeoath5Jwc7TmhQn34NTU\nythhiUbqlkni/PnzrFq1iujoaFJSUrC0tMTHx4ewsDCGDBmCmdmNdzF//nwiIyNxcnJi/fr1AFy5\ncoXnn3+e5ORkPD09+eCDD7Czk7VoReNUotVzObeIgkItCoVSsPNIMruOXqSFiy0vPOBL0yaWxg5T\nNGI3XXTo//7v/zhx4gRDhw7lnnvuwdnZmaKiImJjY9m9ezcnT57kn//8J76+vpWWP3jwILa2tsyd\nO9eQJN555x0cHBx47LHH+Pzzz8nOzuaFF16oUrAyBryULKhSpj7WRVpWAd/8GsP5S7lk55dU+prW\nbnbMmeBLE+uqj16qj3VRU6QuytTookODBg1iwYIFFbZ37NiR++67j6ysLM6fP3/D8v7+/iQnJ5fb\ntm3bNr777jsAIiIimDx5cpWThBD1XWzyFT76+Rg5+SW4OlrTwqUJjnaW2FiZYaLRoNGAjZU5g/xa\nyPBWUSfcNEkMGDDgpoUdHBxwcLi9hUsyMzNxdi7tgHNxceHy5cu3VV6I+upAzCWWbDiJTqeYPKQD\nIX6exg5JiFuq0li6f//73+Tk5KDVannwwQfx9fVl7dq1NR2bEPVadl4xO48k882vMfxr2YHS9R1M\nNDw7rrskCFFvVGl0U1RUFC+99BKRkZG4ubnx/vvvM2PGDEaNGnXbB3RyciI9PR1nZ2fS0tJo1qxZ\nlcve7bW1hkTqokxdrIviEh3zv9hHSkY+AGamJnRq04wnx/agjUfN3QhXF+vCWKQuqsdtDYE9cOAA\noaGhuLm5odFoqlTm+n7xgQMHsnr1ambMmMGaNWsYNGhQlY8vHVGlpFOuTF2ti1+jE0nJyCeoiztD\nerWkhYut4Sa4moq3rtaFMUhdlLnbZFmly01OTk688sorbNq0ib59+6LVatHpdLcsN2fOHCZMmEB8\nfDzBwcH8/PPPzJgxg6ioKMLCwti7dy8zZsy4qzcgRF2TW1DChqgEbK3MeDC0Pa3d7eQuaVFvVakl\n8e6777Ju3TrGjh1L06ZNSUpKYtq0aVUqV5lly5bdVpBC1CcbohLIL9LywEBvbGWEkqjnqpQkmjVr\nxsMPP2x47OnpiaendLwJcb20rAK2/5mEc1MrBkrntGgAbtoGfvLJJzl27Filz+Xm5vL111/z448/\n1khgQtRHa3bFodUpRvdvh7mZXGIS9d9NWxLPPPMM7777LgkJCXTv3h0nJyeKioqIi4sjOTmZCRMm\nMHHixNqKVYg6Sa9XHI1NZ+vBJE6du0xrdzsCZH1p0UDcNEn4+PjwxRdfcPHiRfbv309qaiqWlpYM\nHTqUnj17YmFhUVtxClEnnU26wpINJ7mUVQBAp9aOPBTaAZMqjv4Toq6rUp+Eh4fHHd0TIURDlltQ\nwqe/HCc7r4T+PTwY3LMlnq5NjB2WENWqShdNMzIyeOGFF3jooYcAiImJ4YcffqjRwISo677b8jdZ\nucWE92vLw8M6SYIQDVKVksQrr7xCz549yc7OBqBdu3Z8//33NRqYEHXZvpMp7D91Ce8WTRnWu5Wx\nwxGixlQpSaSmpjJx4kRMTU0BsLCwwMRERm6Ixikzu5DvfjuNpbkpj97fCVP5XxANWJXO7usXFsrO\nzq4w3YYQjUFqZj6frD5OfpGWCYO8cXW0MXZIQtSoKnVcDxkyhP/7v/8jLy+P1atX8/333zNmzJia\njk2IOkOr0/Pb/kTW7k5Aq9PTp6s7/Xs0N3ZYQtS4KiWJRx99lHXr1pGdnc3OnTuZPHmyjHYSjYJS\nimOxGfy8M46ktFzsbS14KLQD/h1dqjzJpRD1WZVngR05ciQjR46syViEqDZanf6uJtXT6vT8FZfJ\nuj3xJKSUziZ6b3cPmY9JNDpVShIZGRl89913JCYmotVqDds//PDDGgtMiDuVmJrDv5Yd4KmIbvh1\ncKlSGaUUv/wRz7HYDC7nFBrWntYAvXxcGdG3DZ4uMsRVND5VShJPPvkknTt3JigoyDDCSYi66kzS\nFZSCY7EZVU4S6/YksD4qAXMzE5rZWdLc2Ra3ZjYM6ukpyUE0alVKEgUFBbz22ms1HYsQ1eLS5dIp\nMs6lVm3RmX0nU1i7Ox7npla8MsUfe1uZbkaIq6qUJHr06MHff/9Nx44dazoeIe5a2v/mUUpOy63Q\nN/H7gfOsj0qgh7cTfbt6YGZmwpcbY7C2NOXZsd0lQQhxnSoliQkTJjBp0iTc3d2xtLQ0bF+1atUd\nH3jZsmWsWrUKjUZDhw4dePPNN2XCQFEtrk62p9UpLqTn0cqtbPnGvSdSyC0oYc/xFPYcTwFAo4Fn\nRvWghVxWEqKCKiWJF198kZkzZ9K5c+dq6ZNITU3l22+/ZfPmzVhYWPDcc8+xadMmwsPD73rfonHT\nK2VoSUDpJaerSaKwWEtiai5eze0ZG+zF7uMXOR6bQXj/dnRt52SskIWo06qUJCwtLXnkkUeq9cB6\nvZ6CggJMTEwoLCzE1dW1WvcvGqcrucWUaPU42VuRkV1IYmqu4bnYC9nolaJDSwc6tnKkYytHI0Yq\nRP1QpYHk/fr1Y9euXdV2UDc3N6ZNm0ZwcDD9+/fHzs6OPn36VNv+ReN16XI+APd0cMZEoynXeX3m\nfBYA7T0djBKbEPVRlVoSP/30E59//jm2trZYWFiglEKj0bB37947Omh2djbbtm1jx44d2NnZ8cwz\nz7B+/XpGjBhxR/sT4qqr/RGeLk3wcLLhfGoueqUw0Wg4k3QFAG/PpsYMUYh6pUpJ4ueff67Wg0ZF\nRdGyZUscHEp/0YWGhnL48OFbJgkXF7ubPt+YSF2UubYu8or1ALRv04xzl3JJPpRECRrcm9kSdzGb\nVu52tG3VzFih1jg5L8pIXVSPKiWJFi1aVOtBmzdvztGjRykqKsLCwoJ9+/bRrVu3W5ZLS6vauPeG\nzsXFTurif66vi4Tk0ktKFoCbgzUAh0+l4OZoQ1GxjnYe9g227uS8KCN1UeZuk+VNk8SLL77IO++8\nw5gxYyqdzOxOh8B2796dsLAwwsPDMTMzo3PnzowfP/6O9iXEtdKyCjAz1eBoZ0lrt9IhrYmpuVzJ\nLQagvVxqEuK23DRJXLp0CYB//OMf1X7gp59+mqeffrra9ysat7SsQpyaWmNioqGla+kvqHMpOVhb\nlp7qHaTTWojbctMkcXW50oCAgFoJRoi7kV+oJbeghLYe9gDYWJnh6mBNYmoOJiYamtlb4tTUyshR\nClG/yLqLosG4ehOd6//6IgBauduRV6glJ79EWhFC3IGbtiROnz5NUFBQhe13OwRWiJpwdfiri2NZ\nkmjt1oSDMaWXTaU/Qojbd9Mk0aZNGz7//PPaikWI25KTX4xdcdn6JldvpLu2JdH6mnmb2reUloQQ\nt+umScLCwqLah78KUR2KS3S8siQab08HZo0uHT6dVklL4uq8TbZWZjR3tq39QIWo526aJMzNZZlG\nUTfFJGaRk1/C4dNpnEvJobW7nWEdCZdrOqftbS3o29UdFwdrTGRNaiFu2007rn/66afaikOI23Is\nNt3w9+8HzwOlLQlHO0sszMvPVPzI/Z0ZeW/bWo1PiIZCRjeJekcpxbHYDKwtzWjh0oTok6mkXykg\nM7sIl2v6I4QQd0+ShKh3Lmbkk36lkC5tmzGqfzt0esWqyFgU4OIg90EIUZ0kSYh651hsBgA9vJwI\n6dkSWysz9p8qHebqKi0JIaqVJAlR71ztj+jazgkrSzMG+JaNwLt2ZJMQ4u5JkhD1Sn6hljNJV2jr\nYUdT29I10Qf6tcDUpHTkkquDjTHDE6LBkSQh6pWTCZno9Ipu16xJ3czeij5d3bG2NMPDSZKEENWp\nSutJCFFXGPojvJ3LbZ8ytCMPDGxvmO1VCFE95D9K1Bt6pTgel4G9jTmt3csvpGJqYoKNlTSMhahu\n8l8l6o0T8ZlcySumWzsnuXtaiFpitJZETk4OL7/8MmfOnMHExIRFixbRo0cPY4Uj6jCtTs+GqAQ2\nRJ1DAwR1dTd2SEI0GkZLEgsXLmTAgAF89NFHaLVaCgsLjRWKqMMupOexdONJ4i/m4GRvySPDO+PT\n2tHYYQnRaBglSeTm5nLw4EH+/e9/lwZhZkaTJk2MEYqoo/ILtazbE8+2Q0no9IqgLu48FNoBGyvp\nRhOiNhnlPy4pKQlHR0fmzZtHTEwMXbt25eWXX8bKSqZUaOwKirTsO5nK2j/iyM4vwbmpFRMHteee\nDi7GDk2IRkmjlFK1fdC//vqLBx54gBUrVtCtWzcWLlyInZ0dzzzzTG2HIuoApRSH/05j+8Hz7P3r\nIsUlOqwsTBk3qAPhA7wqzOoqhKg9RmlJuLu74+7uTrdupYvFhIWFsWTJkluWS0vLqenQ6gUXF7sG\nUxdZuUV8ufEUf8VnAuDmaE1QV3f6dW+Oo50lV7Lyb1q+IdXF3ZK6KCN1UcbFxe7WL7oJoyQJZ2dn\nPDw8iI+Pp23btuzbtw8vLy9jhCKM6PDpNL7aHENuQQld2zVjVN+2tGtuj0aGtwpRZxitF/CVV17h\nhRdeQKvV0rJlS958801jhSJqWVGxjhXbz7DzyAXMzUx4KLQDA/1aSHIQog4yWpLw8fHh559/Ntbh\nhZEkpGSzeN1JUjPz8XRpwuMjO9PCRUa2CVFXyXhCUSu0Oj2/7U/klz/i0ekVYQEtGd3fC3Mzuelf\niLpMkoSoUVeXGv1x+1lSMvNp2sSCR4d3pkvbZsYOTQhRBZIkRI1JTM1hVWQsf8VnotFAiF8LIvq1\no4m1ubFDE0JUkSQJUe0SU3NYtyeBP0+nAdCljSMPDGqPp/Q9CFHvSJIQ1SY9q4CVkbEciCldb9qr\nuT2j7m1Ll7bNZOSSEPWUJAlx1wqKtGzad47f9p9Hq9PT1sOO8H7t6CrJQYh6T5KEuGNKKfaeSGFl\nZCxXcotxtLNkbLAXgZ3dZL0HIRoISRLijpxLyWH576c5m3wFczMTRvZtw7DA1lhayDxLQjQkkiTE\nbSko0rJmVxzbDiWhgJ4dXHhgoDfODtbGDk0IUQMkSYgqOxabwbe/xZCRXYRbMxsmDelAlzZyv4MQ\nDZkkCXFLl3OK+GnHWaJPpmJqouH+Pm0Y0ac15mZyaUmIhk6ShLghrU7P1oNJrN0TT1GxjrYe9kwb\n5oOnq9zvIERjIUlCVKCU4sjZdH7eGceF9DyaWJszYag3/Xo0l1FLQjQykiREOafOXWb1zlhiL2Sj\n0cAA3+aMGeAlU2kI0UhJkhBA6Qpxy7ec5tD/ptLo2cGF8P7taOFsa+TIhBDGJEmikVNK8cexi/y4\n/SwFRVraezZlwqD2tPWwN3ZoQog6wKhJQq/XM2bMGNzc3Pjss8+MGUqjdC4lhxXbzvD3+SysLEyZ\nHNaRAb7S7yCEKGPUJPHNN9/g5eVFbm6uMcNodDKzC1mzK46ov1JQgK+3M5OGdKCZvZWxQxNC1DFG\nSxIpKSns3LmTmTNn8tVXXxkrjEalqETHb9GJbNp3jmKtHk+XJjwwyFtuiBNC3JDRksSiRYuYO3cu\nOTk5xgqh0VBKcSDmEj/tOEtmdhFNbS14KLQdfbt5YGIil5aEEDdmlCQRGRmJs7MznTp1Ijo6usrl\nXFzsajCq+qUqdaGUYv+JFFZsPc3Z81mYmZowdmB7xg1qj41VwxnSKudFGamLMlIX1UOjlFK1fdD3\n3nuPdevWYWpqSlFREXl5eYSGhvL222/ftFxamrQ6oPTkv1VdHDmTzpo/4jh/KRcN4O/jypgB7XB1\ntKmdIGtJVeqisZC6KCN1UeZuk6VRksS19u/fz5dfflml0U3yoZe62T/A5Zwilv9+mj9Pp6HRQGAn\nN4b3adNg73eQL4MyUhdlpC7K3G2SkPskGgi9Uuw8coFVkWcpKNLRoaUDU8I60ryBJgchRO0wepII\nCAggICDA2GHUa38nXuaHbWdITM3F2tKMqUM7yjxLQohqYfQkIe7cpawCVu44y6G/S6fSCOrixthg\nbxztLI0cmRCioZAkUQ/lF5awKjKWLQcS0eoUXi3smTioA+2ay1QaQojqJUmiHlFKEfVXCqt3xXE5\npwhHO0vGhXgR2MkNjVxaEkLUAEkS9URyeh7f/vY3p89nYWFuyqh72zI0sBWW5rI6nBCi5kiSqOOK\ninWsj0rgt/2J6PSKe9o78/QD96DR6owdmhCiEZAkUUcppYg+mcrKyFgu5xThZG/FQ6Ed8G3vjIuj\njYwBF0LUCkkSddC5lByW/36as8lXMDM14f4+bRjeuzWWFnJpSQhRuyRJ1CG5BSWs3hXHzsPJKKBn\nRxceCPHG2cHa2KEJIRopSRJ1gFanZ/exi6zeFUduQQkeTjY8FNqBzjKFtxDCyCRJGJFWpyfqrxQ2\nRCWQfqUQSwtTxod4M9jfEzNTE2OHJ4QQkiSM5fCZNH7Yeob0K4WYmZowuKcn9wW1xqGJ3C0thKg7\nJEnUsit5xXz/+2kOxFzC1ETDoJ6e3Ne7tUylIYSokyRJ1BK9XvHHsQusiowlr1CLVwt7Hh7WqcFO\n4S2EaBgkSdSC0+ez+H7raRJTc7G0MOWh0A6E+LWQWVqFEHWeJIkalJZVwM87Y9l/6hIAQV3cGRvs\nJZeWhBD1hiSJGpBXWMLGqHNsPXQerU7R1sOeBwe3x6tFU2OHJoQQt8UoSSIlJYW5c+eSnp6Oqakp\n48aNY8qUKcYIpVoppfjj2EVW7jhLXqEWJ3tLxgzwIqCzm1xaEkLUS0ZJEqampsybN49OnTqRl5fH\n6NGj6du3L15eXsYIp1qkXs7n680xxCRmYWVhyrhgLwb7e2JuJlNpCCHqL6MkCRcXF1xcXACwtbXF\ny8uLS5cu1cskUVCk5fcD59m47xwlWj2+3s5MGtKBZvZWxg5NCCHumtH7JJKSkoiJiaF79+7GDuW2\nlGh17Dh8gY17E8jJL8He1oJH7++Af0cXWQBICNFgaJRSylgHz8vLY/LkyTz55JMMHjzYWGHclqIS\nHb9Hn+PnHWdJzyrA2tKMiGBvRvVvh42VubHDE0KIamW0JKHVann88cfp378/U6dOrVIZY66hUFyi\nY/ufyfy6P5HsvGIszEwI8WvBfb1bY2djUauxuLjYyXoS/yN1UUbqoozURRkXF7u7Km+0y03z58/H\n29u7ygnCmOIvZrNkw0kuZuRjZWHK8KDWhPq3xN62dpODEELUNqMkiUOHDrF+/Xo6dOhAeHg4Go2G\n559/nv79+xsjnBvS6vSs25PApr3n0CvF4J6ejOrXFlu5rCSEaCSMkiR69uzJqVOnjHHoKjubdIVv\nfoshKS0PJ3srpg/vRKfWjsYOSwghapXRRzfVNbkFJayKPMuuoxcB6N+jOQ8M9MbaUqpKCNH4yDff\nNf48ncayzTHkFpTg6WLL5LCOtPd0MHZYQghhNJIkKL0h7oetZ9h9/CLmZiaMC/Ei1L+lrA4nhGj0\nGn2SOJmQybLNMaRfKaSVWxMeG9FF1ngQQoj/abRJIuNKIT9uP8PBv9PQAMODWjPq3rbSehBCiGs0\nuiRRotXz6/5ENkYlUKzV49XCnkmhHWntfnc3nAghREPUqJLEsdh0vt96hkuXC7C3tWBymBdBXd1l\nGm8hhLiBRpEk0rIKWLHtDIfPpGOi0RDq35JR97bFxqpRvH0hhLhjDfpbsqhEx+Z959i0LxGtTk+H\nlg5MCu2Ap2sTY4cmhBD1QoNMEkopDv2dxo/bz5KRXYhDEwvGD/QmsJObTOMthBC3ocEliYSUbFZs\nPcPppCuYmmgYFtiK+/u0kTumhRDiDjSYb84recX8HBnLnuMXUcA97Z0ZH+KNWzMbY4cmhBD1Vr1P\nElqdnu2Hkli7J56CIh2eLrZMGNSezm2aGTs0IYSo9+p1kvg78TLfbjnNhfQ8bK3MmDSkAwN8m2Nq\nIjfECSFEdaiXSSK3oISfdpxl97GLaIBg3+ZE9G9X6yvECSFEQ2e0JLFr1y4WLVqEUooxY8YwY8aM\nW5ZRShF9MpXvt54ht6CElq5NmDrUh3bN7WshYiGEaHyMkiT0ej2vv/46y5Ytw9XVlbFjxzJo0CC8\nvLxuWCbjSgEfrTrG0dgMLMxNGB/iTWgvT7m0JIQQNcgoSeLYsWO0bt2aFi1aADB8+HC2bdt20yTx\n1NvbySvU0qm1I1OH+eDqYF1b4QohRKNllCSRmpqKh4eH4bGbmxvHjx+/aRm9gqlDO9K/R3O5IU4I\nIWqJUZKEUuq2yyx9JZTCvKIaiEYIIcSNGCVJuLu7c+HCBcPj1NRUXF1db1rGzsZCRi9dw8VFpja/\nSuqijNRFGamL6mGUXt9u3bqRmJhIcnIyxcXFbNy4kUGDBhkjFCGEEDdhlJaEqakpr776KtOnT0cp\nxdixY2/aaS2EEMI4NOpOOgiEEEI0CnKTgRBCiBuSJCGEEOKGJEkIIYS4oTqfJHbt2sXQoUMJCwvj\n888/N3Y4tSolJYUpU6Zw3333MWLECL755hsArly5wvTp0wkLC+ORRx4hJyfHyJHWHr1eT0REBDNn\nzgQgKSmJ8ePHExYWxuzZs9FqtUaOsHbk5OTwzDPPMGzYMIYPH87Ro0cb7XmxbNky7r//fkaMGMGc\nOXMoLi5uNOfF/Pnz6dOnDyNGjDBsu9l58MYbbzBkyBBGjRrFqVOnqnSMOp0krs7xtHTpUjZs2MDG\njRuJjY01dli1xtTUlHnz5rFp0yZWrFjB8uXLiY2N5fPPPycoKIjffvuNwMBAFi9ebOxQa80333xT\nbiTcf/7zH6ZNm8Zvv/2GnZ0dq1atMmJ0tWfhwoUMGDCAzZs3s3btWtq1a9coz4vU1FS+/fZbVq9e\nzfr169HpdGzcuLHRnBejR49m6dKl5bbd6DzYuXMniYmJbNmyhQULFvDaa69V6Rh1OklcO8eTubm5\nYY6nxsLFxYVOnToBYGtri5eXF6mpqWzbto2IiAgAIiIi2Lp1qzHDrDUpKSns3LmTcePGGbbt27eP\nsLAwoLQufv/9d2OFV2tyc3M5ePAgY8aMAcDMzAw7O7tGe17o9XoKCgrQarUUFhbi6upKdHR0ozgv\n/P39sbcvPwv29efB1e/Mbdu2ER4eDkCPHj3IyckhPT39lseo00misjmeLl26ZMSIjCcpKYmYmBh6\n9OhBRkYGzs7OQGkiuXz5spGjqx2LFi1i7ty5hrm7Ll++TNOmTTH530zA7u7ujeL8SEpKwtHRkXnz\n5hEREcGrr75KQUFBozwv3NzcmDZtGsHBwfTv3x87Ozs6d+6Mvb19ozsvrsrMzCx3HmRmZgJw6dIl\n3N3dDa9zc3MjNTX1lvur00lCbuEolZeXxzPPPMP8+fOxtbVtlBMcRkZG4uzsTKdOnQznhVKqwjnS\nGOpGq9Vy8uRJHnzwQdasWYO1tTWff/55o3jv18vOzmbbtm3s2LGDP/74g4KCAnbt2lXhdY2xbq5X\n2fdpVeqlTq9MdydzPDU0Wq2WZ555hlGjRjF48GAAnJycSE9Px9nZmbS0NJo1a/jref/5559s376d\nnTt3UlRURF5eHosWLSInJwe9Xo+JiQkpKSmN4vxwd3fH3d2dbt26ATBkyBC++OKLRnleREVF0bJl\nSxwcHAAYPHgwhw8fJjs7u9GdF1fd6Dxwc3MjJSXF8Lqq1kudbknIHE+loxe8vb2ZOnWqYdvAgQNZ\nvXo1AGvWrGkUdTJ79mwiIyPZtm0b7733HoGBgfznP/8hMDCQX3/9FWg8deHs7IyHhwfx8fFAab+M\nt7d3ozwvmjdvztGjRykqKkIpxb59+2jfvn2jOi+ubyHc6DwYNGgQv/zyCwBHjhzB3t7ecFnqZur8\ntBy7du1i4cKFhjmeqrLMaUNx6NAhJk2aRIcOHdBoNGg0Gp5//nm6d+/Oc889x8WLF2nevDkffvhh\nhc6rhmz//v18+eWXfPbZZ5w/f57Zs2eTnZ1Np06deOeddzA3Nzd2iDUuJiaGl19+Ga1WS8uWLXnz\nzTfR6XSN8rz45JNP2LhxI2ZmZnTu3Jk33niDlJSURnFezJkzh+joaLKysnB2dmbWrFkMHjyYZ599\nttLzYMGCBfzxxx9YW1vz5ptv0qVLl1seo84nCSGEEMZTpy83CSGEMC5JEkIIIW5IkoQQQogbkiQh\nhBDihiRJCCGEuCFJEkIIIW5IkoSocwYOHMjZs2dr5ViffPJJuWmk582bx/Lly+96v/PmzWPEiBHM\nnj37rvd1MzExMWzevLlGjyEaN0kSolH75JNPKCkpqdZ9pqens2XLFtavX897771Xrfu+3smTJ+84\nSej1+mqORjREkiREvREfH89jjz3GuHHjCA8PN0w9AODj48PixYsZO3YsoaGhbNmyxfDcb7/9xrBh\nwxg9ejSLFy/Gx8eHgoICFixYgEajYcKECURERJCbmwvA6dOnmTp1KmFhYbz00ks3jOeXX35hxIgR\njBo1ilmzZpGZmUleXh5Tp06lqKiIiIgIvv7663Jl1q5dy9NPP214rNPp6Nevn2GOsiVLljB+/HhG\njx7NE088QUZGBgAlJSW89dZbjBgxgvDwcGbNmkVWVhYff/wx+/btIyIigoULFwKlsxREREQwatQo\npk2bxvnz54HSO9XDw8N54403mDBhAn/88cfdfByisVBC1DEhISHqzJkz5bZptVoVERGh4uLilFJK\n5ebmqrCwMMPjjh07quXLlyullDp06JDq16+fUkqp9PR0FRAQoBITE5VSSn311VfKx8dH5efnG8oV\nFGMkQHAAAAOxSURBVBQYjvPSSy+pBx98UBUXF6vi4mI1fPhwFRUVVSHG06dPq3vvvVelp6crpZT6\n4IMP1HPPPaeUUiopKUn17t270vdWUFCgevfurS5fvqyUUmr79u1q6tSpSiml1q5dq1599VXDa7//\n/ns1Z84cpZRSH3/8sZo1a5bSarVKKWUov3r1avXMM88YymRkZKjevXur2NhYpZRSK1euVOPGjVNK\nKRUdHa06d+6sjh49WmlsQlRGWhKiXkhISCAuLo7Zs2cTHh7OQw89RElJSbmVCu+77z4AfH19SUtL\no7i4mKNHj9K1a1datmwJwNixYyvsW103M83gwYMxNzfH3Nyczp07k5iYWKFMdHQ0wcHBODk5ATBh\nwgSioqJu+T6srKwYNGgQGzZsAEonYLu6eND27dvZu3cv4eHhhIeH8/3333Px4kWgdKr0KVOmYGpq\nCmCY9fR6R48epVOnTrRr1w6AMWPGcOrUKfLz8wFo3bo13bt3v2WcQlxVp6cKF+IqpRTNmjVjzZo1\nlT6v0WiwtLQEMCw2o9PpKiSA6x9XxsLCwvC3qalppesjK6UqzMV/9bi3Eh4ezptvvsn999/P/v37\neeeddwz7fOKJJxg9enSlx6uKyuK69rGNjU2V9iPEVdKSEPVC27ZtsbKyYu3atYZtcXFx5OXlARW/\nRK8+9vX15cSJE4br8tf2YwA0adKk3ELxVRUUFMTOnTsNfQY//vgjffr0qXD8yvj7+5Obm8t7771H\naGioIbkNHDiQ77//nuzsbACKi4uJiYkBICQkhG+++cbQyX511bkmTZoY+lKuvt9Tp04ZphFfvXo1\nnTt3luQg7pi0JESdo9FoePjhhzEzMzP8Ml6/fj2fffYZCxcu5Msvv0Sn0+Hs7MwHH3xgKHP9PqB0\nAZZ//etfzJgxA0dHR4KDgzEzM8Pa2hqAadOmMWXKFKytrfn222+rHKO3tzezZ8/m4YcfxsTEhJYt\nW7JgwYIKx7+R8PBwPvroo/9v5w5xGASiIAwPBoMhHADNBRCcgtUEzQWQSByChAOgSHB4joVBLqlo\ngnumadK0/T/51LrZyeat1nW9Z2VZ6jgO1XWtIAh0XZeqqlKWZWqaRuM4yjmnMAyVpqmmaVJRFJrn\nWc455Xmurus0DIPatpX3XkmS3E0FeAVfhePnneepKIokPW/W27a9ZRcC+Ac0Cfy8ZVm077u894rj\nWH3ff/pIwNegSQAATDxcAwBMhAQAwERIAABMhAQAwERIAABMhAQAwPQAVSnSA55bZkwAAAAASUVO\nRK5CYII=\n",
+ "text/plain": [
+ "\u003cmatplotlib.figure.Figure at 0x7f47b8e3bd90\u003e"
+ ]
+ },
+ "metadata": {
+ "tags": []
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "plt.plot(eager_means)\n",
+ "plt.ylabel('Time(s)')\n",
+ "plt.xlabel('Length of vector')\n",
+ "_ = plt.title('Time to sum the elements of 1000 vectors (Eager)')\n",
+ "_ = plt.ylim(ymin=0)"
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "collapsed_sections": [],
+ "default_view": {},
+ "name": "Autograph vs. Eager vs Graph sum",
+ "provenance": [
+ {
+ "file_id": "1olZkm32B7n7pQwlIAXR0_w8fZhRHCtkX",
+ "timestamp": 1531755808890
+ }
+ ],
+ "version": "0.3.2",
+ "views": {}
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/tensorflow/contrib/autograph/impl/api.py b/tensorflow/contrib/autograph/impl/api.py
index f7fe3de5da..0adff76a9f 100644
--- a/tensorflow/contrib/autograph/impl/api.py
+++ b/tensorflow/contrib/autograph/impl/api.py
@@ -23,7 +23,6 @@ from functools import wraps
from enum import Enum
# pylint:disable=g-bad-import-order
-import gast
import six
# pylint:enable=g-bad-import-order
@@ -245,37 +244,41 @@ def to_graph(e,
_, name, namespace = conversion.entity_to_graph(e, program_ctx, arg_values,
arg_types)
- module = gast.Module([])
+ nodes = []
for dep in reversed(program_ctx.dependency_cache.values()):
- module.body.append(dep)
- compiled_node, compiled_src = compiler.ast_to_object(
- module, source_prefix=program_ctx.required_imports)
+ nodes.extend(dep)
+ compiled_module, compiled_src = compiler.ast_to_object(
+ nodes,
+ source_prefix=program_ctx.required_imports,
+ include_source_map=True)
# The compiled code should see everything the entry entity saw.
# TODO(mdan): This might not work well if the call tree spans modules?
for key, val in namespace.items():
# Avoid overwriting entities that have been transformed.
- if key not in compiled_node.__dict__:
- compiled_node.__dict__[key] = val
- compiled_fn = getattr(compiled_node, name)
+ if key not in compiled_module.__dict__:
+ compiled_module.__dict__[key] = val
+ compiled = getattr(compiled_module, name)
# Need this so the source_mapping attribute is available for the context
# manager to access for runtime errors.
#
# Note that compiler.ast_to_object attaches the source map 'ag_source_map__'
# symbol to the compiled module.
+ # TODO(mdan): Record this statically in the generated code.
+ # TODO(mdan): Rename this attribute to 'autograph_info__'
source_map_attribute_name = 'ag_source_map'
- if getattr(compiled_fn, source_map_attribute_name, None) is not None:
+ if getattr(compiled, source_map_attribute_name, None) is not None:
raise ValueError('cannot convert %s because is has an attribute '
'"%s", which is reserved for AutoGraph.' %
- (compiled_fn, source_map_attribute_name))
- setattr(compiled_fn, source_map_attribute_name,
- compiled_node.__dict__['ag_source_map__'])
+ (compiled, source_map_attribute_name))
+ setattr(compiled, source_map_attribute_name,
+ compiled_module.__dict__['ag_source_map__'])
if verbose:
logging.info('Compiled output of %s:\n\n%s\n', e, compiled_src)
- return compiled_fn
+ return compiled
def to_code(e,
@@ -308,7 +311,7 @@ def to_code(e,
conversion.entity_to_graph(e, program_ctx, arg_values, arg_types)
code = '\n'.join(
- compiler.ast_to_source(dep, indentation)[0]
+ compiler.ast_to_source(dep, indentation)
for dep in reversed(tuple(six.itervalues(program_ctx.dependency_cache))))
return program_ctx.required_imports + '\n\n' + code
diff --git a/tensorflow/contrib/autograph/impl/api_test.py b/tensorflow/contrib/autograph/impl/api_test.py
index 4de7df6572..754baa87b0 100644
--- a/tensorflow/contrib/autograph/impl/api_test.py
+++ b/tensorflow/contrib/autograph/impl/api_test.py
@@ -280,6 +280,20 @@ class ApiTest(test.TestCase):
x = tc.test_method()
self.assertEqual(1, sess.run(x))
+ def test_converted_call_already_converted(self):
+
+ def f(x):
+ return x == 0
+
+ with self.test_session() as sess:
+ x = api.converted_call(f, False, False, {}, constant_op.constant(0))
+ self.assertTrue(sess.run(x))
+
+ converted_f = api.to_graph(f)
+ x = api.converted_call(converted_f, False, False, {},
+ constant_op.constant(0))
+ self.assertTrue(sess.run(x))
+
def test_to_graph_basic(self):
def test_fn(x, s):
diff --git a/tensorflow/contrib/autograph/impl/conversion.py b/tensorflow/contrib/autograph/impl/conversion.py
index 7bd0ba3f2d..afb10d4d8b 100644
--- a/tensorflow/contrib/autograph/impl/conversion.py
+++ b/tensorflow/contrib/autograph/impl/conversion.py
@@ -48,6 +48,7 @@ from tensorflow.contrib.autograph.pyct import inspect_utils
from tensorflow.contrib.autograph.pyct import origin_info
from tensorflow.contrib.autograph.pyct import parser
from tensorflow.contrib.autograph.pyct import qual_names
+from tensorflow.contrib.autograph.pyct import templates
from tensorflow.contrib.autograph.pyct import transformer
from tensorflow.python.util import tf_inspect
@@ -70,6 +71,8 @@ def is_whitelisted_for_graph(o):
for prefix, in config.DEFAULT_UNCOMPILED_MODULES:
if m.__name__.startswith(prefix):
return True
+ if hasattr(o, 'autograph_info__'):
+ return True
return False
@@ -120,7 +123,16 @@ def entity_to_graph(o, program_ctx, arg_values, arg_types):
'Entity "%s" has unsupported type "%s". Only functions and classes are '
'supported for now.' % (o, type(o)))
+ # TODO(mdan): This is temporary. it should be created using a converter.
+ # TODO(mdan): The attribute should be added with a helper, not directly.
+ # The helper can ensure there are no collisions.
+ template = '''
+ entity.autograph_info__ = {}
+ '''
+ node.extend(templates.replace(template, entity=name))
+
program_ctx.add_to_cache(o, node)
+
if program_ctx.recursive:
while True:
candidate = None
@@ -164,7 +176,7 @@ def class_to_graph(c, program_ctx):
class_namespace = namespace
else:
class_namespace.update(namespace)
- converted_members[m] = node
+ converted_members[m] = node[0]
namer = program_ctx.new_namer(class_namespace)
class_name = namer.compiled_class_name(c.__name__, c)
@@ -175,10 +187,10 @@ def class_to_graph(c, program_ctx):
# program_ctx.update_name_map(namer)).
output_nodes = []
renames = {}
- bases = []
+ base_names = []
for base in c.__bases__:
if isinstance(object, base):
- bases.append('object')
+ base_names.append('object')
continue
if is_whitelisted_for_graph(base):
alias = namer.new_symbol(base.__name__, ())
@@ -190,28 +202,28 @@ def class_to_graph(c, program_ctx):
else:
# This will trigger a conversion into a class with this name.
alias = namer.compiled_class_name(base.__name__, base)
- bases.append(alias)
+ base_names.append(alias)
renames[qual_names.QN(base.__name__)] = qual_names.QN(alias)
program_ctx.update_name_map(namer)
# Generate the definition of the converted class.
- output_nodes.append(
- gast.ClassDef(
- class_name,
- bases=bases,
- keywords=[],
- body=list(converted_members.values()),
- decorator_list=[]))
- node = gast.Module(output_nodes)
-
+ bases = [gast.Name(n, gast.Load(), None) for n in base_names]
+ class_def = gast.ClassDef(
+ class_name,
+ bases=bases,
+ keywords=[],
+ body=list(converted_members.values()),
+ decorator_list=[])
# Make a final pass to replace references to the class or its base classes.
# Most commonly, this occurs when making super().__init__() calls.
# TODO(mdan): Making direct references to superclass' superclass will fail.
- node = qual_names.resolve(node)
+ class_def = qual_names.resolve(class_def)
renames[qual_names.QN(c.__name__)] = qual_names.QN(class_name)
- node = ast_util.rename_symbols(node, renames)
+ class_def = ast_util.rename_symbols(class_def, renames)
- return node, class_name, class_namespace
+ output_nodes.append(class_def)
+
+ return output_nodes, class_name, class_namespace
def _add_reserved_symbol(namespace, name, entity):
@@ -268,18 +280,18 @@ def function_to_graph(f,
context = converter.EntityContext(namer, entity_info, program_ctx)
node = node_to_graph(node, context, rewrite_errors=rewrite_errors)
- # TODO(mdan): This somewhat duplicates the call rename logic in call_treest.py
+ # TODO(mdan): This somewhat duplicates the call rename logic in call_trees.py
new_name, did_rename = namer.compiled_function_name(f.__name__, f, owner_type)
if not did_rename:
new_name = f.__name__
if node.name != f.__name__:
raise NotImplementedError('Strange corner case. Send us offending code!')
-
node.name = new_name
+
program_ctx.update_name_map(namer)
# TODO(mdan): Use this at compilation.
- return node, new_name, namespace
+ return [node], new_name, namespace
def node_to_graph(node, context, rewrite_errors=True):
diff --git a/tensorflow/contrib/autograph/impl/conversion_test.py b/tensorflow/contrib/autograph/impl/conversion_test.py
index 207225a1ac..1c5d4d09c4 100644
--- a/tensorflow/contrib/autograph/impl/conversion_test.py
+++ b/tensorflow/contrib/autograph/impl/conversion_test.py
@@ -60,10 +60,11 @@ class ConversionTest(test.TestCase):
return a + b
program_ctx = self._simple_program_ctx()
- ast, name, ns = conversion.entity_to_graph(f, program_ctx, None, None)
- self.assertTrue(isinstance(ast, gast.FunctionDef), ast)
+ nodes, name, ns = conversion.entity_to_graph(f, program_ctx, None, None)
+ fn_node, _ = nodes
+ self.assertIsInstance(fn_node, gast.FunctionDef)
self.assertEqual('tf__f', name)
- self.assertTrue(ns['b'] is b)
+ self.assertIs(ns['b'], b)
def test_entity_to_graph_call_tree(self):
@@ -78,14 +79,11 @@ class ConversionTest(test.TestCase):
self.assertTrue(f in program_ctx.dependency_cache)
self.assertTrue(g in program_ctx.dependency_cache)
- self.assertEqual('tf__f', program_ctx.dependency_cache[f].name)
- # need one extra .body[0] in order to step past the try/except wrapper that
- # is added automatically, the other for the with tf.name_scope('f') that is
- # added automatically
- self.assertEqual(
- 'tf__g',
- program_ctx.dependency_cache[f].body[0].body[0].body[0].value.func.id)
- self.assertEqual('tf__g', program_ctx.dependency_cache[g].name)
+ f_node = program_ctx.dependency_cache[f][0]
+ g_node = program_ctx.dependency_cache[g][0]
+ self.assertEqual('tf__f', f_node.name)
+ self.assertEqual('tf__g', f_node.body[0].body[0].body[0].value.func.id)
+ self.assertEqual('tf__g', g_node.name)
def test_entity_to_graph_class_hierarchy(self):
@@ -117,10 +115,12 @@ class ConversionTest(test.TestCase):
self.assertTrue(TestBase in program_ctx.dependency_cache)
self.assertTrue(TestSubclass in program_ctx.dependency_cache)
+ # The returned nodes will include:
+ # <import nodes>, <class node>, <assignment node>
self.assertEqual('TfTestBase',
- program_ctx.dependency_cache[TestBase].body[-1].name)
+ program_ctx.dependency_cache[TestBase][-2].name)
self.assertEqual('TfTestSubclass',
- program_ctx.dependency_cache[TestSubclass].body[-1].name)
+ program_ctx.dependency_cache[TestSubclass][-2].name)
def test_entity_to_graph_class_hierarchy_whitelisted(self):
@@ -139,10 +139,11 @@ class ConversionTest(test.TestCase):
self.assertTrue(TestSubclass in program_ctx.dependency_cache)
self.assertFalse(training.Model in program_ctx.dependency_cache)
self.assertEqual(
- 'Model',
- program_ctx.dependency_cache[TestSubclass].body[0].names[0].name)
+ 'Model', program_ctx.dependency_cache[TestSubclass][0].names[0].name)
+ # The returned nodes will include:
+ # <import nodes>, <class node>, <assignment node>
self.assertEqual('TfTestSubclass',
- program_ctx.dependency_cache[TestSubclass].body[-1].name)
+ program_ctx.dependency_cache[TestSubclass][-2].name)
def test_entity_to_graph_lambda(self):
f = lambda a: a
diff --git a/tensorflow/contrib/autograph/pyct/BUILD b/tensorflow/contrib/autograph/pyct/BUILD
index f77a6ab392..ddadc6b96e 100644
--- a/tensorflow/contrib/autograph/pyct/BUILD
+++ b/tensorflow/contrib/autograph/pyct/BUILD
@@ -100,6 +100,16 @@ py_test(
)
py_test(
+ name = "origin_info_test",
+ srcs = ["origin_info_test.py"],
+ srcs_version = "PY2AND3",
+ deps = [
+ ":pyct",
+ "//tensorflow/python:client_testlib",
+ ],
+)
+
+py_test(
name = "parser_test",
srcs = ["parser_test.py"],
srcs_version = "PY2AND3",
diff --git a/tensorflow/contrib/autograph/pyct/ast_util.py b/tensorflow/contrib/autograph/pyct/ast_util.py
index 86e3f56a64..d7453b0781 100644
--- a/tensorflow/contrib/autograph/pyct/ast_util.py
+++ b/tensorflow/contrib/autograph/pyct/ast_util.py
@@ -20,7 +20,6 @@ from __future__ import print_function
import ast
-import collections
import gast
from tensorflow.contrib.autograph.pyct import anno
@@ -185,6 +184,7 @@ class PatternMatcher(gast.NodeVisitor):
if v != p:
return self.no_match()
+
def matches(node, pattern):
"""Basic pattern matcher for AST.
@@ -253,30 +253,61 @@ def apply_to_single_assignments(targets, values, apply_fn):
apply_fn(target, values)
-def iter_fields(node):
- for field in sorted(node._fields):
- try:
- yield getattr(node, field)
- except AttributeError:
- pass
-
-
-def iter_child_nodes(node):
- for field in iter_fields(node):
- if isinstance(field, gast.AST):
- yield field
- elif isinstance(field, list):
- for item in field:
- if isinstance(item, gast.AST):
- yield item
-
-
-def parallel_walk(node_a, node_b):
- todo_a = collections.deque([node_a])
- todo_b = collections.deque([node_b])
- while todo_a and todo_b:
- node_a = todo_a.popleft()
- node_b = todo_b.popleft()
- todo_a.extend(iter_child_nodes(node_a))
- todo_b.extend(iter_child_nodes(node_b))
- yield node_a, node_b
+def parallel_walk(node, other):
+ """Walks two ASTs in parallel.
+
+ The two trees must have identical structure.
+
+ Args:
+ node: Union[ast.AST, Iterable[ast.AST]]
+ other: Union[ast.AST, Iterable[ast.AST]]
+ Yields:
+ Tuple[ast.AST, ast.AST]
+ Raises:
+ ValueError: if the two trees don't have identical structure.
+ """
+ if isinstance(node, (list, tuple)):
+ node_stack = list(node)
+ else:
+ node_stack = [node]
+
+ if isinstance(other, (list, tuple)):
+ other_stack = list(other)
+ else:
+ other_stack = [other]
+
+ while node_stack and other_stack:
+ assert len(node_stack) == len(other_stack)
+ n = node_stack.pop()
+ o = other_stack.pop()
+
+ if (not isinstance(n, (ast.AST, gast.AST)) or
+ not isinstance(o, (ast.AST, gast.AST)) or
+ n.__class__.__name__ != o.__class__.__name__):
+ raise ValueError('inconsistent nodes: {} and {}'.format(n, o))
+
+ yield n, o
+
+ for f in n._fields:
+ n_child = getattr(n, f, None)
+ o_child = getattr(o, f, None)
+ if f.startswith('__') or n_child is None or o_child is None:
+ continue
+
+ if isinstance(n_child, (list, tuple)):
+ if (not isinstance(o_child, (list, tuple)) or
+ len(n_child) != len(o_child)):
+ raise ValueError(
+ 'inconsistent values for field {}: {} and {}'.format(
+ f, n_child, o_child))
+ node_stack.extend(n_child)
+ other_stack.extend(o_child)
+
+ elif isinstance(n_child, (gast.AST, ast.AST)):
+ node_stack.append(n_child)
+ other_stack.append(o_child)
+
+ elif n_child != o_child:
+ raise ValueError(
+ 'inconsistent values for field {}: {} and {}'.format(
+ f, n_child, o_child))
diff --git a/tensorflow/contrib/autograph/pyct/ast_util_test.py b/tensorflow/contrib/autograph/pyct/ast_util_test.py
index 981e398b93..2293c89720 100644
--- a/tensorflow/contrib/autograph/pyct/ast_util_test.py
+++ b/tensorflow/contrib/autograph/pyct/ast_util_test.py
@@ -44,7 +44,7 @@ class AstUtilTest(test.TestCase):
node, {qual_names.QN('a'): qual_names.QN('renamed_a')})
self.assertIsInstance(node.body[0].value.left.id, str)
- source, _ = compiler.ast_to_source(node)
+ source = compiler.ast_to_source(node)
self.assertEqual(source.strip(), 'renamed_a + b')
def test_rename_symbols_attributes(self):
@@ -54,7 +54,7 @@ class AstUtilTest(test.TestCase):
node = ast_util.rename_symbols(
node, {qual_names.from_str('b.c'): qual_names.QN('renamed_b_c')})
- source, _ = compiler.ast_to_source(node)
+ source = compiler.ast_to_source(node)
self.assertEqual(source.strip(), 'renamed_b_c = renamed_b_c.d')
def test_rename_symbols_annotations(self):
@@ -97,10 +97,10 @@ class AstUtilTest(test.TestCase):
d = ast_util.keywords_to_dict(keywords)
# Make sure we generate a usable dict node by attaching it to a variable and
# compiling everything.
- output = parser.parse_str('b = 3')
- output.body += (ast.Assign([ast.Name(id='d', ctx=ast.Store())], d),)
- result, _ = compiler.ast_to_object(output)
- self.assertDictEqual(result.d, {'a': 3, 'c': 1, 'd': 'e'})
+ node = parser.parse_str('def f(b): pass').body[0]
+ node.body.append(ast.Return(d))
+ result, _ = compiler.ast_to_object(node)
+ self.assertDictEqual(result.f(3), {'a': 3, 'c': 1, 'd': 'e'})
def assertMatch(self, target_str, pattern_str):
node = parser.parse_expression(target_str)
@@ -130,8 +130,8 @@ class AstUtilTest(test.TestCase):
'super(Bar, _).__init__(_)')
def _mock_apply_fn(self, target, source):
- target, _ = compiler.ast_to_source(target)
- source, _ = compiler.ast_to_source(source)
+ target = compiler.ast_to_source(target)
+ source = compiler.ast_to_source(source)
self._invocation_counts[(target.strip(), source.strip())] += 1
def test_apply_to_single_assignments_dynamic_unpack(self):
@@ -157,24 +157,40 @@ class AstUtilTest(test.TestCase):
})
def test_parallel_walk(self):
- ret = ast.Return(
- ast.BinOp(
- op=ast.Add(),
- left=ast.Name(id='a', ctx=ast.Load()),
- right=ast.Num(1)))
- node = ast.FunctionDef(
- name='f',
- args=ast.arguments(
- args=[ast.Name(id='a', ctx=ast.Param())],
- vararg=None,
- kwarg=None,
- defaults=[]),
- body=[ret],
- decorator_list=[],
- returns=None)
+ node = parser.parse_str(
+ textwrap.dedent("""
+ def f(a):
+ return a + 1
+ """))
for child_a, child_b in ast_util.parallel_walk(node, node):
self.assertEqual(child_a, child_b)
+ def test_parallel_walk_inconsistent_trees(self):
+ node_1 = parser.parse_str(
+ textwrap.dedent("""
+ def f(a):
+ return a + 1
+ """))
+ node_2 = parser.parse_str(
+ textwrap.dedent("""
+ def f(a):
+ return a + (a * 2)
+ """))
+ node_3 = parser.parse_str(
+ textwrap.dedent("""
+ def f(a):
+ return a + 2
+ """))
+ with self.assertRaises(ValueError):
+ for _ in ast_util.parallel_walk(node_1, node_2):
+ pass
+ # There is not particular reason to reject trees that differ only in the
+ # value of a constant.
+ # TODO(mdan): This should probably be allowed.
+ with self.assertRaises(ValueError):
+ for _ in ast_util.parallel_walk(node_1, node_3):
+ pass
+
if __name__ == '__main__':
test.main()
diff --git a/tensorflow/contrib/autograph/pyct/cfg.py b/tensorflow/contrib/autograph/pyct/cfg.py
index 25fec7fd53..ba51dcf285 100644
--- a/tensorflow/contrib/autograph/pyct/cfg.py
+++ b/tensorflow/contrib/autograph/pyct/cfg.py
@@ -67,10 +67,8 @@ class Node(object):
if isinstance(self.ast_node, gast.FunctionDef):
return 'def %s' % self.ast_node.name
elif isinstance(self.ast_node, gast.withitem):
- source, _ = compiler.ast_to_source(self.ast_node.context_expr)
- return source.strip()
- source, _ = compiler.ast_to_source(self.ast_node)
- return source.strip()
+ return compiler.ast_to_source(self.ast_node.context_expr).strip()
+ return compiler.ast_to_source(self.ast_node).strip()
class Graph(
diff --git a/tensorflow/contrib/autograph/pyct/common_transformers/BUILD b/tensorflow/contrib/autograph/pyct/common_transformers/BUILD
index ca1441cf6f..a0938b3e5f 100644
--- a/tensorflow/contrib/autograph/pyct/common_transformers/BUILD
+++ b/tensorflow/contrib/autograph/pyct/common_transformers/BUILD
@@ -24,6 +24,7 @@ py_library(
deps = [
"//tensorflow/contrib/autograph/pyct",
"@gast_archive//:gast",
+ "@six_archive//:six",
],
)
diff --git a/tensorflow/contrib/autograph/pyct/common_transformers/anf.py b/tensorflow/contrib/autograph/pyct/common_transformers/anf.py
index cc039986c2..e42f679cfe 100644
--- a/tensorflow/contrib/autograph/pyct/common_transformers/anf.py
+++ b/tensorflow/contrib/autograph/pyct/common_transformers/anf.py
@@ -12,12 +12,24 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
-"""Conversion to A-normal form."""
+"""Conversion to A-normal form.
+
+The general idea of A-normal form is that every intermediate value is
+explicitly named with a variable. For more, see
+https://en.wikipedia.org/wiki/A-normal_form.
+
+The specific converters used here are based on Python AST semantics as
+documented at https://greentreesnakes.readthedocs.io/en/latest/.
+"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
+import gast
+import six
+
+from tensorflow.contrib.autograph.pyct import templates
from tensorflow.contrib.autograph.pyct import transformer
@@ -32,26 +44,375 @@ class DummyGensym(object):
# * the symbols generated so far
self._idx = 0
- def new_name(self, stem):
+ def new_name(self, stem='tmp'):
self._idx += 1
return stem + '_' + str(1000 + self._idx)
class AnfTransformer(transformer.Base):
- """Performs the actual conversion."""
+ """Performs the conversion to A-normal form (ANF)."""
- # TODO(mdan): Link to a reference.
- # TODO(mdan): Implement.
+ # The algorithm is a postorder recursive tree walk. Any given node A may, in
+ # general, require creation of a series B of Assign statements, which compute
+ # and explicitly name the intermediate values needed to compute the value of
+ # A. If A was already a statement, it can be replaced with the sequence B +
+ # [A]. If A was an expression, B needs to be propagated up the tree until a
+ # statement is encountered. Since the `ast.NodeTransformer` framework makes
+ # no provision for subtraversals returning side information, this class
+ # accumulates the sequence B in an instance variable.
- def __init__(self, entity_info):
- """Creates a transformer.
+ # The only other subtlety is that some Python statements (like `if`) have both
+ # expression fields (`test`) and statement list fields (`body` and `orelse`).
+ # Any additional assignments needed to name all the intermediate values in the
+ # `test` can be prepended to the `if` node, but assignments produced by
+ # processing the `body` and the `orelse` need to be kept together with them,
+ # and not accidentally lifted out of the `if`.
+
+ def __init__(self, entity_info, gensym_source=None):
+ """Creates an ANF transformer.
Args:
entity_info: transformer.EntityInfo
+ gensym_source: An optional object with the same interface as `DummyGensym`
+ for generating unique names
"""
super(AnfTransformer, self).__init__(entity_info)
- self._gensym = DummyGensym(entity_info)
+ if gensym_source is None:
+ self._gensym = DummyGensym(entity_info)
+ else:
+ self._gensym = gensym_source(entity_info)
+ self._pending_statements = []
+
+ def _consume_pending_statements(self):
+ ans = self._pending_statements
+ self._pending_statements = []
+ return ans
+
+ def _add_pending_statement(self, stmt):
+ self._pending_statements.append(stmt)
+
+ _trivial_nodes = (
+ # Non-nodes that show up as AST fields
+ bool, six.string_types,
+ # Leaf nodes that are already in A-normal form
+ gast.expr_context, gast.Name, gast.Num, gast.Str, gast.Bytes,
+ gast.NameConstant, gast.Ellipsis,
+ # Binary operators
+ gast.Add, gast.Sub, gast.Mult, gast.Div, gast.Mod, gast.Pow, gast.LShift,
+ gast.RShift, gast.BitOr, gast.BitXor, gast.BitAnd, gast.FloorDiv,
+ # Unary operators
+ gast.Invert, gast.Not, gast.UAdd, gast.USub,
+ # Comparison operators
+ gast.Eq, gast.NotEq, gast.Lt, gast.LtE, gast.Gt, gast.GtE,
+ gast.Is, gast.IsNot, gast.In, gast.NotIn,
+ )
+
+ def _is_node_trivial(self, node):
+ if node is None:
+ return True
+ elif isinstance(node, self._trivial_nodes):
+ return True
+ elif isinstance(node, gast.keyword):
+ return self._is_node_trivial(node.value)
+ elif isinstance(node, (gast.Starred, gast.withitem, gast.slice)):
+ return self._are_children_trivial(node)
+ return False
+
+ def _are_children_trivial(self, node):
+ for field in node._fields:
+ if not field.startswith('__'):
+ if not self._is_node_trivial(getattr(node, field)):
+ return False
+ return True
+
+ def _ensure_node_is_trivial(self, node):
+ if node is None:
+ return node
+ elif isinstance(node, self._trivial_nodes):
+ return node
+ elif isinstance(node, list):
+ # If something's field was actually a list, e.g., variadic arguments.
+ return [self._ensure_node_is_trivial(n) for n in node]
+ elif isinstance(node, gast.keyword):
+ node.value = self._ensure_node_is_trivial(node.value)
+ return node
+ elif isinstance(node, (gast.Starred, gast.withitem, gast.slice)):
+ return self._ensure_fields_trivial(node)
+ elif isinstance(node, gast.expr):
+ temp_name = self._gensym.new_name()
+ temp_assign = templates.replace(
+ 'temp_name = expr', temp_name=temp_name, expr=node)[0]
+ self._add_pending_statement(temp_assign)
+ answer = templates.replace('temp_name', temp_name=temp_name)[0]
+ return answer
+ else:
+ raise ValueError('Do not know how to treat {}'.format(node))
+
+ def _ensure_fields_trivial(self, node):
+ for field in node._fields:
+ if field.startswith('__'):
+ continue
+ setattr(node, field, self._ensure_node_is_trivial(getattr(node, field)))
+ return node
+
+ def _visit_strict_statement(self, node, trivialize_children=True):
+ assert not self._pending_statements
+ node = self.generic_visit(node)
+ if trivialize_children:
+ self._ensure_fields_trivial(node)
+ results = self._consume_pending_statements()
+ results.append(node)
+ return results
+
+ def _visit_strict_expression(self, node):
+ node = self.generic_visit(node)
+ self._ensure_fields_trivial(node)
+ return node
+
+ # Note on code order: These are listed in the same order as the grammar
+ # elements on https://github.com/serge-sans-paille/gast
+
+ # FunctionDef, AsyncFunctionDef, and ClassDef should be correct by default.
+
+ def visit_Return(self, node):
+ return self._visit_strict_statement(node)
+
+ def visit_Delete(self, node):
+ return self._visit_strict_statement(node, trivialize_children=False)
+
+ def visit_Assign(self, node):
+ return self._visit_strict_statement(node, trivialize_children=False)
+
+ def visit_AugAssign(self, node):
+ return self._visit_strict_statement(node, trivialize_children=False)
+
+ def visit_Print(self, node):
+ return self._visit_strict_statement(node)
+
+ def visit_For(self, node):
+ assert not self._pending_statements
+ # It's important to visit node.iter first, because any statements created
+ # thereby need to live outside the body.
+ self.visit(node.iter)
+ node.iter = self._ensure_node_is_trivial(node.iter)
+ iter_stmts = self._consume_pending_statements()
+ # This generic_visit will revisit node.iter, but that is both correct and
+ # cheap because by this point node.iter is trivial.
+ node = self.generic_visit(node)
+ assert not self._pending_statements
+ iter_stmts.append(node)
+ return iter_stmts
+
+ def visit_AsyncFor(self, node):
+ if not self._are_children_trivial(node):
+ msg = ('Nontrivial AsyncFor nodes not supported yet '
+ '(need to think through the semantics).')
+ raise ValueError(msg)
+ return self.generic_visit(node)
+
+ def visit_While(self, node):
+ if not self._is_node_trivial(node.test):
+ msg = ('While with nontrivial test not supported yet '
+ '(need to avoid precomputing the test).')
+ raise ValueError(msg)
+ return self.generic_visit(node)
+
+ def visit_If(self, node):
+ assert not self._pending_statements
+ # It's important to visit node.test first, because any statements created
+ # thereby need to live outside the body.
+ self.visit(node.test)
+ node.test = self._ensure_node_is_trivial(node.test)
+ condition_stmts = self._consume_pending_statements()
+ # This generic_visit will revisit node.test, but that is both correct and
+ # cheap because by this point node.test is trivial.
+ node = self.generic_visit(node)
+ assert not self._pending_statements
+ condition_stmts.append(node)
+ return condition_stmts
+
+ def visit_With(self, node):
+ assert not self._pending_statements
+ # It's important to visit node.items first, because any statements created
+ # thereby need to live outside the body.
+ for item in node.items:
+ self.visit(item)
+ node.items = [self._ensure_node_is_trivial(n) for n in node.items]
+ contexts_stmts = self._consume_pending_statements()
+ # This generic_visit will revisit node.items, but that is both correct and
+ # cheap because by this point node.items is trivial.
+ node = self.generic_visit(node)
+ assert not self._pending_statements
+ contexts_stmts.append(node)
+ return contexts_stmts
+
+ def visit_AsyncWith(self, node):
+ if not self._are_children_trivial(node):
+ msg = ('Nontrivial AsyncWith nodes not supported yet '
+ '(need to think through the semantics).')
+ raise ValueError(msg)
+ return self.generic_visit(node)
+
+ def visit_Raise(self, node):
+ return self._visit_strict_statement(node)
+
+ # Try should be correct by default.
+
+ def visit_Assert(self, node):
+ if not self._are_children_trivial(node):
+ msg = ('Nontrivial Assert nodes not supported yet '
+ '(need to avoid computing the test when assertions are off, and '
+ 'avoid computing the irritant when the assertion does not fire).')
+ raise ValueError(msg)
+ return self.generic_visit(node)
+
+ # Import and ImportFrom should be correct by default.
+
+ def visit_Exec(self, node):
+ return self._visit_strict_statement(node)
+
+ # Global and Nonlocal should be correct by default.
+
+ def visit_Expr(self, node):
+ return self._visit_strict_statement(node, trivialize_children=False)
+
+ # Pass, Break, and Continue should be correct by default.
+
+ def visit_BoolOp(self, node):
+ if not self._are_children_trivial(node):
+ msg = ('Nontrivial BoolOp nodes not supported yet '
+ '(need to preserve short-circuiting semantics).')
+ raise ValueError(msg)
+ return self.generic_visit(node)
+
+ def visit_BinOp(self, node):
+ return self._visit_strict_expression(node)
+
+ def visit_UnaryOp(self, node):
+ return self._visit_strict_expression(node)
+
+ def visit_Lambda(self, node):
+ if not self._are_children_trivial(node):
+ msg = ('Nontrivial Lambda nodes not supported '
+ '(cannot insert statements into lambda bodies).')
+ raise ValueError(msg)
+ return self.generic_visit(node)
+
+ def visit_IfExp(self, node):
+ if not self._are_children_trivial(node):
+ msg = ('Nontrivial IfExp nodes not supported yet '
+ '(need to convert to If statement, to evaluate branches lazily '
+ 'and insert statements into them).')
+ raise ValueError(msg)
+ return self.generic_visit(node)
+
+ def visit_Dict(self, node):
+ return self._visit_strict_expression(node)
+
+ def visit_Set(self, node):
+ return self._visit_strict_expression(node)
+
+ def visit_ListComp(self, node):
+ msg = ('ListComp nodes not supported '
+ '(need to convert to a form that tolerates '
+ 'assignment statements in clause bodies).')
+ raise ValueError(msg)
+
+ def visit_SetComp(self, node):
+ msg = ('SetComp nodes not supported '
+ '(need to convert to a form that tolerates '
+ 'assignment statements in clause bodies).')
+ raise ValueError(msg)
+
+ def visit_DictComp(self, node):
+ msg = ('DictComp nodes not supported '
+ '(need to convert to a form that tolerates '
+ 'assignment statements in clause bodies).')
+ raise ValueError(msg)
+
+ def visit_GeneratorExp(self, node):
+ msg = ('GeneratorExp nodes not supported '
+ '(need to convert to a form that tolerates '
+ 'assignment statements in clause bodies).')
+ raise ValueError(msg)
+
+ def visit_Await(self, node):
+ if not self._are_children_trivial(node):
+ msg = ('Nontrivial Await nodes not supported yet '
+ '(need to think through the semantics).')
+ raise ValueError(msg)
+ return self.generic_visit(node)
+
+ def visit_Yield(self, node):
+ return self._visit_strict_expression(node)
+
+ def visit_YieldFrom(self, node):
+ if not self._are_children_trivial(node):
+ msg = ('Nontrivial YieldFrom nodes not supported yet '
+ '(need to unit-test them in Python 2).')
+ raise ValueError(msg)
+ return self.generic_visit(node)
+
+ def visit_Compare(self, node):
+ if len(node.ops) > 1:
+ msg = ('Multi-ary compare nodes not supported yet '
+ '(need to preserve short-circuiting semantics).')
+ raise ValueError(msg)
+ return self._visit_strict_expression(node)
+
+ def visit_Call(self, node):
+ return self._visit_strict_expression(node)
+
+ def visit_Repr(self, node):
+ if not self._are_children_trivial(node):
+ msg = ('Nontrivial Repr nodes not supported yet '
+ '(need to research their syntax and semantics).')
+ raise ValueError(msg)
+ return self.generic_visit(node)
+
+ def visit_FormattedValue(self, node):
+ if not self._are_children_trivial(node):
+ msg = ('Nontrivial FormattedValue nodes not supported yet '
+ '(need to unit-test them in Python 2).')
+ raise ValueError(msg)
+ return self.generic_visit(node)
+
+ def visit_JoinedStr(self, node):
+ if not self._are_children_trivial(node):
+ msg = ('Nontrivial JoinedStr nodes not supported yet '
+ '(need to unit-test them in Python 2).')
+ raise ValueError(msg)
+ return self.generic_visit(node)
+
+ def visit_Attribute(self, node):
+ return self._visit_strict_expression(node)
+
+ def visit_Subscript(self, node):
+ return self._visit_strict_expression(node)
+
+ # Starred and Name are correct by default, because the right thing to do is to
+ # just recur.
+
+ def visit_List(self, node):
+ return self._visit_strict_expression(node)
+
+ def visit_Tuple(self, node):
+ return self._visit_strict_expression(node)
+
+
+def transform(node, entity_info, gensym_source=None):
+ """Converts the given node to A-normal form (ANF).
+
+ The general idea of A-normal form: https://en.wikipedia.org/wiki/A-normal_form
+ The specific converters used here are based on Python AST semantics as
+ documented at https://greentreesnakes.readthedocs.io/en/latest/.
-def transform(node, entity_info):
- return AnfTransformer(entity_info).visit(node)
+ Args:
+ node: The node to transform.
+ entity_info: transformer.EntityInfo. TODO(mdan): What information does this
+ argument provide?
+ gensym_source: An optional object with the same interface as `DummyGensym`
+ for generating unique names.
+ """
+ return AnfTransformer(entity_info, gensym_source=gensym_source).visit(node)
diff --git a/tensorflow/contrib/autograph/pyct/common_transformers/anf_test.py b/tensorflow/contrib/autograph/pyct/common_transformers/anf_test.py
index 81983a5ecb..951974820c 100644
--- a/tensorflow/contrib/autograph/pyct/common_transformers/anf_test.py
+++ b/tensorflow/contrib/autograph/pyct/common_transformers/anf_test.py
@@ -18,6 +18,8 @@ from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
+import textwrap
+
from tensorflow.contrib.autograph.pyct import compiler
from tensorflow.contrib.autograph.pyct import parser
from tensorflow.contrib.autograph.pyct import transformer
@@ -25,6 +27,22 @@ from tensorflow.contrib.autograph.pyct.common_transformers import anf
from tensorflow.python.platform import test
+class DummyGensym(object):
+ """A dumb gensym that suffixes a stem by sequential numbers from 1000."""
+
+ def __init__(self, entity_info):
+ del entity_info
+ # A proper implementation needs to account for:
+ # * entity_info.namespace
+ # * all the symbols defined in the AST
+ # * the symbols generated so far
+ self._idx = 0
+
+ def new_name(self, stem='tmp'):
+ self._idx += 1
+ return stem + '_' + str(1000 + self._idx)
+
+
class AnfTransformerTest(test.TestCase):
def _simple_source_info(self):
@@ -37,17 +55,349 @@ class AnfTransformerTest(test.TestCase):
owner_type=None)
def test_basic(self):
-
def test_function():
a = 0
return a
-
node, _ = parser.parse_entity(test_function)
- node = anf.transform(node, self._simple_source_info())
+ node = anf.transform(node.body[0], self._simple_source_info())
result, _ = compiler.ast_to_object(node)
-
self.assertEqual(test_function(), result.test_function())
+ def assert_same_ast(self, expected_node, node, msg=None):
+ expected_source = compiler.ast_to_source(expected_node, indentation=' ')
+ expected_str = textwrap.dedent(expected_source).strip()
+ got_source = compiler.ast_to_source(node, indentation=' ')
+ got_str = textwrap.dedent(got_source).strip()
+ self.assertEqual(expected_str, got_str, msg=msg)
+
+ def assert_body_anfs_as_expected(self, expected_fn, test_fn):
+ # Testing the code bodies only. Wrapping them in functions so the
+ # syntax highlights nicely, but Python doesn't try to execute the
+ # statements.
+ exp_node, _ = parser.parse_entity(expected_fn)
+ node, _ = parser.parse_entity(test_fn)
+ node = anf.transform(
+ node, self._simple_source_info(), gensym_source=DummyGensym)
+ exp_name = exp_node.body[0].name
+ # Ignoring the function names in the result because they can't be
+ # the same (because both functions have to exist in the same scope
+ # at the same time).
+ node.body[0].name = exp_name
+ self.assert_same_ast(exp_node, node)
+ # Check that ANF is idempotent
+ node_repeated = anf.transform(
+ node, self._simple_source_info(), gensym_source=DummyGensym)
+ self.assert_same_ast(node_repeated, node)
+
+ def test_binop_basic(self):
+
+ def test_function(x, y, z):
+ a = x + y + z
+ return a
+
+ def expected_result(x, y, z):
+ tmp_1001 = x + y
+ a = tmp_1001 + z
+ return a
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ def test_if_basic(self):
+
+ def test_function(a, b, c, e, f, g):
+ if a + b + c:
+ d = e + f + g
+ return d
+
+ def expected_result(a, b, c, e, f, g):
+ tmp_1001 = a + b
+ tmp_1002 = tmp_1001 + c
+ if tmp_1002:
+ tmp_1003 = e + f
+ d = tmp_1003 + g
+ return d
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ def test_nested_binop_and_return(self):
+
+ def test_function(b, c, d, e):
+ return (2 * b + c) + (d + e)
+
+ def expected_result(b, c, d, e):
+ tmp_1001 = 2 * b
+ tmp_1002 = tmp_1001 + c
+ tmp_1003 = d + e
+ tmp_1004 = tmp_1002 + tmp_1003
+ return tmp_1004
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ def test_function_call_and_expr(self):
+
+ def test_function(call_something, a, b, y, z, c, d, e, f, g, h, i):
+ call_something(a + b, y * z, kwarg=c + d, *(e + f), **(g + h + i))
+
+ def expected_result(call_something, a, b, y, z, c, d, e, f, g, h, i):
+ tmp_1001 = g + h
+ tmp_1002 = a + b
+ tmp_1003 = y * z
+ tmp_1004 = e + f
+ tmp_1005 = c + d
+ tmp_1006 = tmp_1001 + i
+ call_something(tmp_1002, tmp_1003, kwarg=tmp_1005, *tmp_1004, **tmp_1006)
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ def test_with_and_print(self):
+
+ def test_function(a, b, c):
+ with a + b + c as d:
+ print(2 * d + 1)
+
+ def expected_result(a, b, c):
+ tmp_1001 = a + b
+ tmp_1002 = tmp_1001 + c
+ with tmp_1002 as d:
+ tmp_1003 = 2 * d
+ tmp_1004 = tmp_1003 + 1
+ print(tmp_1004)
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ def test_local_definition_and_binary_compare(self):
+
+ def test_function():
+ def foo(a, b):
+ return 2 * a < b
+ return foo
+
+ def expected_result():
+ def foo(a, b):
+ tmp_1001 = 2 * a
+ tmp_1002 = tmp_1001 < b
+ return tmp_1002
+ return foo
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ def test_list_literal(self):
+
+ def test_function(a, b, c, d, e, f):
+ return [a + b, c + d, e + f]
+
+ def expected_result(a, b, c, d, e, f):
+ tmp_1001 = a + b
+ tmp_1002 = c + d
+ tmp_1003 = e + f
+ tmp_1004 = [tmp_1001, tmp_1002, tmp_1003]
+ return tmp_1004
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ def test_tuple_literal_and_unary(self):
+
+ def test_function(a, b, c, d, e, f):
+ return (a + b, -(c + d), e + f)
+
+ def expected_result(a, b, c, d, e, f):
+ tmp_1001 = c + d
+ tmp_1002 = a + b
+ tmp_1003 = -tmp_1001
+ tmp_1004 = e + f
+ tmp_1005 = (tmp_1002, tmp_1003, tmp_1004)
+ return tmp_1005
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ def test_set_literal(self):
+
+ def test_function(a, b, c, d, e, f):
+ return set(a + b, c + d, e + f)
+
+ def expected_result(a, b, c, d, e, f):
+ tmp_1001 = a + b
+ tmp_1002 = c + d
+ tmp_1003 = e + f
+ tmp_1004 = set(tmp_1001, tmp_1002, tmp_1003)
+ return tmp_1004
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ def test_dict_literal_and_repr(self):
+
+ def test_function(foo, bar, baz):
+ return repr({foo + bar + baz: 7 | 8})
+
+ def expected_result(foo, bar, baz):
+ tmp_1001 = foo + bar
+ tmp_1002 = tmp_1001 + baz
+ tmp_1003 = 7 | 8
+ tmp_1004 = {tmp_1002: tmp_1003}
+ tmp_1005 = repr(tmp_1004)
+ return tmp_1005
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ def test_field_read_and_write(self):
+
+ def test_function(a, d):
+ a.b.c = d.e.f + 3
+
+ def expected_result(a, d):
+ tmp_1001 = a.b
+ tmp_1002 = d.e
+ tmp_1003 = tmp_1002.f
+ tmp_1001.c = tmp_1003 + 3
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ def test_subscript_read_and_write(self):
+
+ def test_function(a, b, c, d, e, f):
+ a[b][c] = d[e][f] + 3
+
+ def expected_result(a, b, c, d, e, f):
+ tmp_1001 = a[b]
+ tmp_1002 = d[e]
+ tmp_1003 = tmp_1002[f]
+ tmp_1001[c] = tmp_1003 + 3
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ def test_augassign_and_delete(self):
+
+ def test_function(a, x, y, z):
+ a += x + y + z
+ del a
+ del z[y][x]
+
+ def expected_result(a, x, y, z):
+ tmp_1001 = x + y
+ a += tmp_1001 + z
+ del a
+ tmp_1002 = z[y]
+ del tmp_1002[x]
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ def test_raise_yield_and_raise(self):
+
+ def test_function(a, c, some_computed, exception):
+ yield a ** c
+ raise some_computed('complicated' + exception)
+
+ def expected_result(a, c, some_computed, exception):
+ tmp_1001 = a ** c
+ yield tmp_1001
+ tmp_1002 = 'complicated' + exception
+ tmp_1003 = some_computed(tmp_1002)
+ raise tmp_1003
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ def test_with_and_if_with_expressions(self):
+
+ def test_function(foo, bar, function, quux, quozzle, w, x, y, z):
+ with foo + bar:
+ function(x + y)
+ if quux + quozzle:
+ function(z / w)
+
+ def expected_result(foo, bar, function, quux, quozzle, w, x, y, z):
+ tmp_1001 = foo + bar
+ with tmp_1001:
+ tmp_1002 = x + y
+ function(tmp_1002)
+ tmp_1003 = quux + quozzle
+ if tmp_1003:
+ tmp_1004 = z / w
+ function(tmp_1004)
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ def test_exec(self):
+
+ def test_function():
+ # The point is to test A-normal form conversion of exec
+ # pylint: disable=exec-used
+ exec('computed' + 5 + 'stuff', globals(), locals())
+
+ def expected_result():
+ # pylint: disable=exec-used
+ tmp_1001 = 'computed' + 5
+ tmp_1002 = tmp_1001 + 'stuff'
+ tmp_1003 = globals()
+ tmp_1004 = locals()
+ exec(tmp_1002, tmp_1003, tmp_1004)
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ def test_simple_while_and_assert(self):
+
+ def test_function(foo, quux):
+ while foo:
+ assert quux
+ foo = foo + 1 * 3
+
+ def expected_result(foo, quux):
+ while foo:
+ assert quux
+ tmp_1001 = 1 * 3
+ foo = foo + tmp_1001
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ def test_for(self):
+
+ def test_function(compute, something, complicated, foo):
+ for foo in compute(something + complicated):
+ bar = foo + 1 * 3
+ return bar
+
+ def expected_result(compute, something, complicated, foo):
+ tmp_1001 = something + complicated
+ tmp_1002 = compute(tmp_1001)
+ for foo in tmp_1002:
+ tmp_1003 = 1 * 3
+ bar = foo + tmp_1003
+ return bar
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
+ # This test collects several examples where the definition of A-normal form
+ # implemented by this transformer is questionable. Mostly it's here to spell
+ # out what the definition is in these cases.
+ def test_controversial(self):
+
+ def test_function(b, c, d, f):
+ a = c + d
+ a.b = c + d
+ a[b] = c + d
+ a += c + d
+ a, b = c
+ a, b = c, d
+ a = f(c)
+ a = f(c + d)
+ a[b + d] = f.e(c + d)
+
+ def expected_result(b, c, d, f):
+ a = c + d
+ a.b = c + d # Should be a.b = tmp? (Definitely not tmp = c + d)
+ a[b] = c + d # Should be a[b] = tmp? (Definitely not tmp = c + d)
+ a += c + d # Should be a += tmp? (Definitely not tmp = c + d)
+ a, b = c # Should be a = c[0], b = c[1]? Or not?
+ a, b = c, d # Should be a = c, b = d? Or not?
+ a = f(c)
+ tmp_1001 = c + d
+ a = f(tmp_1001)
+ tmp_1002 = b + d
+ tmp_1003 = f.e
+ tmp_1004 = c + d
+ a[tmp_1002] = tmp_1003(tmp_1004) # Or should be a[tmp1] = tmp2?
+
+ self.assert_body_anfs_as_expected(expected_result, test_function)
+
if __name__ == '__main__':
test.main()
diff --git a/tensorflow/contrib/autograph/pyct/compiler.py b/tensorflow/contrib/autograph/pyct/compiler.py
index c90a5e89c2..f9cee10962 100644
--- a/tensorflow/contrib/autograph/pyct/compiler.py
+++ b/tensorflow/contrib/autograph/pyct/compiler.py
@@ -30,44 +30,7 @@ import tempfile
import astor
import gast
-from tensorflow.contrib.autograph.pyct import anno
-from tensorflow.contrib.autograph.pyct import ast_util
from tensorflow.contrib.autograph.pyct import origin_info
-from tensorflow.contrib.autograph.pyct import parser
-
-
-def _build_source_map(node, code):
- """Return the Python objects represented by given AST.
-
- Compiling the AST code this way ensures that the source code is readable by
- e.g. `pdb` or `inspect`.
-
- Args:
- node: An AST node of the original generated code, before the source code is
- generated.
- code: The string representation of the source code for the newly generated
- code.
-
- Returns:
- Dict[CodeLocation, OriginInfo], a mapping between the user and AutoGraph
- generated code.
- """
- # After we have the final generated code we reparse it to get the final line
- # numbers. Then we walk through the generated and original ASTs in parallel
- # to build the mapping between the user and generated code.
- new_node = parser.parse_str(code)
- origin_info.resolve(new_node, code)
- source_mapping = {}
- for before, after in ast_util.parallel_walk(node, new_node):
- # Need both checks because if origin information is ever copied over to new
- # nodes then we need to rely on the fact that only the original user code
- # has the origin annotation.
- if (anno.hasanno(before, anno.Basic.ORIGIN) and
- anno.hasanno(after, anno.Basic.ORIGIN)):
- source_info = anno.getanno(before, anno.Basic.ORIGIN)
- new_line_number = anno.getanno(after, anno.Basic.ORIGIN).line_number
- source_mapping[new_line_number] = source_info
- return source_mapping
def ast_to_source(node, indentation=' '):
@@ -81,24 +44,28 @@ def ast_to_source(node, indentation=' '):
code: The source code generated from the AST object
source_mapping: A mapping between the user and AutoGraph generated code.
"""
- original_node = node
- if isinstance(node, gast.AST):
- node = gast.gast_to_ast(node)
+ if not isinstance(node, (list, tuple)):
+ node = (node,)
generator = astor.codegen.SourceGenerator(indentation, False,
astor.string_repr.pretty_string)
- generator.visit(node)
- generator.result.append('\n')
+
+ for n in node:
+ if isinstance(n, gast.AST):
+ n = gast.gast_to_ast(n)
+ generator.visit(n)
+ generator.result.append('\n')
+
# In some versions of Python, literals may appear as actual values. This
# ensures everything is string.
code = map(str, generator.result)
code = astor.source_repr.pretty_source(code).lstrip()
- source_mapping = _build_source_map(original_node, code)
- return code, source_mapping
+ return code
-def ast_to_object(node,
+def ast_to_object(nodes,
indentation=' ',
+ include_source_map=False,
source_prefix=None,
delete_on_exit=True):
"""Return the Python objects represented by given AST.
@@ -107,42 +74,46 @@ def ast_to_object(node,
e.g. `pdb` or `inspect`.
Args:
- node: The code to compile, as an AST object.
- indentation: The string to use for indentation.
- source_prefix: Optional string to print as-is into the source file.
- delete_on_exit: Whether to delete the temporary file used for compilation on
- exit.
+ nodes: Union[ast.AST, Iterable[ast.AST]], the code to compile, as an AST
+ object.
+ indentation: Text, the string to use for indentation.
+ include_source_map: bool, whether to attach a source map to the compiled
+ object. Also see origin_info.py.
+ source_prefix: Optional[Text], string to print as-is into the source file.
+ delete_on_exit: bool, whether to delete the temporary file used for
+ compilation on exit.
Returns:
- compiled_node: A module object containing the compiled source code.
+ compiled_nodes: A module object containing the compiled source code.
source: The source code of the compiled object
Raises:
ValueError: If ag_source_map__ is already in the namespace of the compiled
- node.
+ nodes.
"""
- # code_source_mapping does not yet include the offsets from import statements.
- source, code_source_mapping = ast_to_source(node, indentation=indentation)
+ if not isinstance(nodes, (list, tuple)):
+ nodes = (nodes,)
+
+ source = ast_to_source(nodes, indentation=indentation)
+
+ if source_prefix:
+ source = source_prefix + '\n' + source
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
- # TODO(znado): move into an _offset_source_map() helper function.
- # Need to offset the generated line numbers by the number of import lines.
- if source_prefix:
- num_import_lines = source_prefix.count('\n') + 1
- else:
- num_import_lines = 0
- source_mapping = {}
- for line_number, original_position in code_source_mapping.items():
- source_map_key = origin_info.CodeLocation(
- file_path=f.name, line_number=line_number + num_import_lines)
- source_mapping[source_map_key] = original_position
module_name = os.path.basename(f.name[:-3])
- if source_prefix:
- f.write(source_prefix)
- f.write('\n')
f.write(source)
+
+ if isinstance(nodes, (list, tuple)):
+ indices = range(-len(nodes), 0)
+ else:
+ indices = (-1,)
+
+ if include_source_map:
+ source_map = origin_info.source_map(nodes, source, f.name, indices)
+
+ # TODO(mdan): Try flush() and delete=False instead.
if delete_on_exit:
atexit.register(lambda: os.remove(f.name))
- compiled_node = imp.load_source(module_name, f.name)
+ compiled_nodes = imp.load_source(module_name, f.name)
# TODO(znado): Clean this up so we don't need to attach it to the namespace.
# TODO(znado): This does not work for classes because their methods share a
@@ -158,11 +129,13 @@ def ast_to_object(node,
# is hard, and this cleanly fixes the
# issues encountered with nested functions because this is attached to the
# outermost one.
- source_map_name = 'ag_source_map__'
- if source_map_name in compiled_node.__dict__:
- raise ValueError('cannot convert %s because is has namespace attribute '
- '"%s", which is reserved for AutoGraph.' %
- (compiled_node, source_map_name))
- compiled_node.__dict__[source_map_name] = source_mapping
-
- return compiled_node, source
+ if include_source_map:
+ # TODO(mdan): This name should be decided by the caller.
+ source_map_name = 'ag_source_map__'
+ if source_map_name in compiled_nodes.__dict__:
+ raise ValueError('cannot convert %s because is has namespace attribute '
+ '"%s", which is reserved for AutoGraph.' %
+ (compiled_nodes, source_map_name))
+ compiled_nodes.__dict__[source_map_name] = source_map
+
+ return compiled_nodes, source
diff --git a/tensorflow/contrib/autograph/pyct/compiler_test.py b/tensorflow/contrib/autograph/pyct/compiler_test.py
index e29fa9324c..cf783da6a3 100644
--- a/tensorflow/contrib/autograph/pyct/compiler_test.py
+++ b/tensorflow/contrib/autograph/pyct/compiler_test.py
@@ -59,7 +59,7 @@ class CompilerTest(test.TestCase):
value=gast.Str('c'))
])
- source, _ = compiler.ast_to_source(node, indentation=' ')
+ source = compiler.ast_to_source(node, indentation=' ')
self.assertEqual(
textwrap.dedent("""
if 1:
diff --git a/tensorflow/contrib/autograph/pyct/origin_info.py b/tensorflow/contrib/autograph/pyct/origin_info.py
index 614e346634..9f98e48a6a 100644
--- a/tensorflow/contrib/autograph/pyct/origin_info.py
+++ b/tensorflow/contrib/autograph/pyct/origin_info.py
@@ -22,49 +22,115 @@ import collections
import gast
from tensorflow.contrib.autograph.pyct import anno
+from tensorflow.contrib.autograph.pyct import ast_util
+from tensorflow.contrib.autograph.pyct import parser
from tensorflow.python.util import tf_inspect
-class CodeLocation(
- collections.namedtuple('CodeLocation', ('file_path', 'line_number'))):
- """Location of a line of code.
+class LineLocation(
+ collections.namedtuple('LineLocation', ('filename', 'lineno'))):
+ """Similar to Location, but without column information.
Attributes:
- file_path: text, the full path to the file containing the code.
- line_number: Int, the 1-based line number of the code in its file.
+ filename: Text
+ lineno: int, 1-based
"""
pass
+class Location(
+ collections.namedtuple('Location', ('filename', 'lineno', 'col_offset'))):
+ """Encodes code location information.
+
+ Attributes:
+ filename: Text
+ lineno: int, 1-based
+ col_offset: int
+ """
+
+ @property
+ def line_loc(self):
+ return LineLocation(self.filename, self.lineno)
+
+
class OriginInfo(
- collections.namedtuple('OriginInfo',
- ('file_path', 'function_name', 'line_number',
- 'column_offset', 'source_code_line'))):
+ collections.namedtuple(
+ 'OriginInfo',
+ ('loc', 'function_name', 'source_code_line'))):
"""Container for information about the source code before conversion.
- Instances of this class contain information about the source code that
- transformed code originated from. Examples include:
- * line number
- * file name
- * original user code
+ Attributes:
+ loc: Location
+ function_name: Optional[Text]
+ source_code_line: Text
"""
def as_frame(self):
- """Makes a traceback frame tuple.
-
- Returns:
- A tuple of (file_path, line_number, function_name, source_code_line).
- """
- return (self.file_path, self.line_number, self.function_name,
+ """Returns a 4-tuple consistent with the return of traceback.extract_tb."""
+ return (self.loc.filename, self.loc.lineno, self.function_name,
self.source_code_line)
+# TODO(mdan): This source map should be a class - easier to refer to.
+def source_map(nodes, code, filename, indices_in_code):
+ """Creates a source map between an annotated AST and the code it compiles to.
+
+ Args:
+ nodes: Iterable[ast.AST, ...]
+ code: Text
+ filename: Optional[Text]
+ indices_in_code: Union[int, Iterable[int, ...]], the positions at which
+ nodes appear in code. The parser always returns a module when parsing
+ code. This argument indicates the position in that module's body at
+ which the corresponding of node should appear.
+
+ Returns:
+ Dict[CodeLocation, OriginInfo], mapping locations in code to locations
+ indicated by origin annotations in node.
+ """
+ reparsed_nodes = parser.parse_str(code)
+ reparsed_nodes = [reparsed_nodes.body[i] for i in indices_in_code]
+
+ resolve(reparsed_nodes, code)
+ result = {}
+
+ for before, after in ast_util.parallel_walk(nodes, reparsed_nodes):
+ # Note: generated code might not be mapped back to its origin.
+ # TODO(mdan): Generated code should always be mapped to something.
+ origin_info = anno.getanno(before, anno.Basic.ORIGIN, default=None)
+ final_info = anno.getanno(after, anno.Basic.ORIGIN, default=None)
+ if origin_info is None or final_info is None:
+ continue
+
+ line_loc = LineLocation(filename, final_info.loc.lineno)
+
+ existing_origin = result.get(line_loc)
+ if existing_origin is not None:
+ # Overlaps may exist because of child nodes, but almost never to
+ # different line locations. Exception make decorated functions, where
+ # both lines are mapped to the same line in the AST.
+
+ # Line overlaps: keep bottom node.
+ if existing_origin.loc.line_loc == origin_info.loc.line_loc:
+ if existing_origin.loc.lineno >= origin_info.loc.lineno:
+ continue
+
+ # In case of overlaps, keep the leftmost node.
+ if existing_origin.loc.col_offset <= origin_info.loc.col_offset:
+ continue
+
+ result[line_loc] = origin_info
+
+ return result
+
+
# TODO(znado): Consider refactoring this into a Visitor.
-def resolve(node, source, function=None):
+# TODO(mdan): Does this work correctly with inner functions?
+def resolve(nodes, source, function=None):
"""Adds an origin information to all nodes inside the body of function.
Args:
- node: The AST node for the function whose body nodes will be annotated.
+ nodes: Union[ast.AST, Iterable[ast.AST, ...]]
source: Text, the source code string for the function whose body nodes will
be annotated.
function: Callable, the function that will have all nodes inside of it
@@ -76,25 +142,32 @@ def resolve(node, source, function=None):
A tuple of the AST node for function and a String containing its source
code.
"""
+ if not isinstance(nodes, (list, tuple)):
+ nodes = (nodes,)
+
if function:
_, function_lineno = tf_inspect.getsourcelines(function)
function_filepath = tf_inspect.getsourcefile(function)
else:
function_lineno = None
function_filepath = None
+
source_lines = source.split('\n')
- for n in gast.walk(node):
- if hasattr(n, 'lineno'):
- # n.lineno is relative to the start of the enclosing function, so need to
- # offset it by the line of the function.
- source_code_line = source_lines[n.lineno - 1]
+ for node in nodes:
+ for n in gast.walk(node):
+ if not hasattr(n, 'lineno'):
+ continue
+
+ lineno_in_body = n.lineno
+
+ source_code_line = source_lines[lineno_in_body - 1]
if function:
- source_lineno = n.lineno + function_lineno - 1
+ source_lineno = function_lineno + lineno_in_body - 1
function_name = function.__name__
else:
- source_lineno = n.lineno
+ source_lineno = lineno_in_body
function_name = None
- anno.setanno(
- n, anno.Basic.ORIGIN,
- OriginInfo(function_filepath, function_name, source_lineno,
- n.col_offset, source_code_line))
+
+ location = Location(function_filepath, source_lineno, n.col_offset)
+ origin = OriginInfo(location, function_name, source_code_line)
+ anno.setanno(n, anno.Basic.ORIGIN, origin)
diff --git a/tensorflow/contrib/autograph/pyct/origin_info_test.py b/tensorflow/contrib/autograph/pyct/origin_info_test.py
new file mode 100644
index 0000000000..6d7d8b1622
--- /dev/null
+++ b/tensorflow/contrib/autograph/pyct/origin_info_test.py
@@ -0,0 +1,101 @@
+# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Tests for origin_info module."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+from tensorflow.contrib.autograph.pyct import anno
+from tensorflow.contrib.autograph.pyct import compiler
+from tensorflow.contrib.autograph.pyct import origin_info
+from tensorflow.contrib.autograph.pyct import parser
+from tensorflow.python.platform import test
+
+
+class OriginInfoTest(test.TestCase):
+
+ def test_source_map(self):
+
+ def test_fn(x):
+ if x > 0:
+ x += 1
+ return x
+
+ node, source = parser.parse_entity(test_fn)
+ fn_node = node.body[0]
+ origin_info.resolve(fn_node, source)
+
+ # Insert a traced line.
+ new_node = parser.parse_str('x = abs(x)').body[0]
+ anno.copyanno(fn_node.body[0], new_node, anno.Basic.ORIGIN)
+ fn_node.body.insert(0, new_node)
+
+ # Insert an untraced line.
+ fn_node.body.insert(0, parser.parse_str('x = 0').body[0])
+
+ modified_source = compiler.ast_to_source(fn_node)
+
+ source_map = origin_info.source_map(fn_node, modified_source,
+ 'test_filename', [0])
+
+ loc = origin_info.LineLocation('test_filename', 1)
+ origin = source_map[loc]
+ self.assertEqual(origin.source_code_line, 'def test_fn(x):')
+ self.assertEqual(origin.loc.lineno, 1)
+
+ # The untraced line, inserted second.
+ loc = origin_info.LineLocation('test_filename', 2)
+ self.assertFalse(loc in source_map)
+
+ # The traced line, inserted first.
+ loc = origin_info.LineLocation('test_filename', 3)
+ origin = source_map[loc]
+ self.assertEqual(origin.source_code_line, ' if x > 0:')
+ self.assertEqual(origin.loc.lineno, 2)
+
+ loc = origin_info.LineLocation('test_filename', 4)
+ origin = source_map[loc]
+ self.assertEqual(origin.source_code_line, ' if x > 0:')
+ self.assertEqual(origin.loc.lineno, 2)
+
+ def test_resolve(self):
+
+ def test_fn(x):
+ """Docstring."""
+ return x # comment
+
+ node, source = parser.parse_entity(test_fn)
+ fn_node = node.body[0]
+ origin_info.resolve(fn_node, source)
+
+ origin = anno.getanno(fn_node, anno.Basic.ORIGIN)
+ self.assertEqual(origin.loc.lineno, 1)
+ self.assertEqual(origin.loc.col_offset, 0)
+ self.assertEqual(origin.source_code_line, 'def test_fn(x):')
+
+ origin = anno.getanno(fn_node.body[0], anno.Basic.ORIGIN)
+ self.assertEqual(origin.loc.lineno, 2)
+ self.assertEqual(origin.loc.col_offset, 2)
+ self.assertEqual(origin.source_code_line, ' """Docstring."""')
+
+ origin = anno.getanno(fn_node.body[1], anno.Basic.ORIGIN)
+ self.assertEqual(origin.loc.lineno, 3)
+ self.assertEqual(origin.loc.col_offset, 2)
+ self.assertEqual(origin.source_code_line, ' return x # comment')
+
+
+if __name__ == '__main__':
+ test.main()
diff --git a/tensorflow/contrib/autograph/pyct/parser.py b/tensorflow/contrib/autograph/pyct/parser.py
index c961efa892..112ed46a1e 100644
--- a/tensorflow/contrib/autograph/pyct/parser.py
+++ b/tensorflow/contrib/autograph/pyct/parser.py
@@ -37,6 +37,7 @@ def parse_entity(entity):
def parse_str(src):
"""Returns the AST of given piece of code."""
+ # TODO(mdan): This should exclude the module things are autowrapped in.
return gast.parse(src)
diff --git a/tensorflow/contrib/autograph/pyct/templates.py b/tensorflow/contrib/autograph/pyct/templates.py
index 72d1d3b269..5831d57ceb 100644
--- a/tensorflow/contrib/autograph/pyct/templates.py
+++ b/tensorflow/contrib/autograph/pyct/templates.py
@@ -140,8 +140,8 @@ class ReplaceTransformer(gast.NodeTransformer):
def _set_inner_child_context(self, node, ctx):
if isinstance(node, gast.Attribute):
- self._set_inner_child_context(node.value, ctx)
- node.ctx = gast.Load()
+ self._set_inner_child_context(node.value, gast.Load())
+ node.ctx = ctx
elif isinstance(node, gast.Tuple):
for e in node.elts:
self._set_inner_child_context(e, ctx)
diff --git a/tensorflow/contrib/autograph/pyct/templates_test.py b/tensorflow/contrib/autograph/pyct/templates_test.py
index a8bbc5a4de..77e8ff62fd 100644
--- a/tensorflow/contrib/autograph/pyct/templates_test.py
+++ b/tensorflow/contrib/autograph/pyct/templates_test.py
@@ -97,6 +97,19 @@ class TemplatesTest(test.TestCase):
with self.assertRaises(ValueError):
templates.replace(template, foo=1)
+ def test_replace_attribute_context(self):
+ template = """
+ def test_fn(foo):
+ foo = 0
+ """
+
+ node = templates.replace(
+ template,
+ foo=parser.parse_expression('a.b.c'))[0]
+ self.assertIsInstance(node.body[0].targets[0].ctx, gast.Store)
+ self.assertIsInstance(node.body[0].targets[0].value.ctx, gast.Load)
+ self.assertIsInstance(node.body[0].targets[0].value.value.ctx, gast.Load)
+
def test_replace_call_keyword(self):
template = """
def test_fn():
diff --git a/tensorflow/contrib/autograph/pyct/testing/BUILD b/tensorflow/contrib/autograph/pyct/testing/BUILD
new file mode 100644
index 0000000000..957db356f7
--- /dev/null
+++ b/tensorflow/contrib/autograph/pyct/testing/BUILD
@@ -0,0 +1,43 @@
+licenses(["notice"]) # Apache 2.0
+
+load("//tensorflow:tensorflow.bzl", "py_test")
+
+filegroup(
+ name = "all_files",
+ srcs = glob(
+ ["**/*"],
+ exclude = [
+ "**/METADATA",
+ "**/OWNERS",
+ ],
+ ),
+ visibility = ["//tensorflow:__subpackages__"],
+)
+
+py_library(
+ name = "testing",
+ srcs = [
+ "codegen.py",
+ ],
+ srcs_version = "PY2AND3",
+ visibility = ["//visibility:public"],
+ deps = [
+ "//tensorflow/contrib/autograph/pyct",
+ "//tensorflow/contrib/autograph/utils",
+ "@gast_archive//:gast",
+ ],
+)
+
+py_test(
+ name = "codegen_test",
+ size = "large",
+ srcs = ["codegen_test.py"],
+ srcs_version = "PY2AND3",
+ tags = ["no_windows"],
+ deps = [
+ ":testing",
+ "//tensorflow/contrib/autograph/pyct",
+ "//tensorflow/python:client_testlib",
+ "@gast_archive//:gast",
+ ],
+)
diff --git a/tensorflow/contrib/autograph/pyct/testing/codegen.py b/tensorflow/contrib/autograph/pyct/testing/codegen.py
new file mode 100644
index 0000000000..279e7c09dc
--- /dev/null
+++ b/tensorflow/contrib/autograph/pyct/testing/codegen.py
@@ -0,0 +1,234 @@
+# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Random code generation for testing/fuzzing."""
+# pylint: disable=invalid-name
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import random
+import string
+
+import gast
+import numpy as np
+
+from tensorflow.contrib.autograph.pyct import templates
+
+
+class NodeSampler(object):
+ sample_map = None
+
+ def sample(self):
+ nodes, magnitudes = zip(*self.sample_map.items())
+ return np.random.choice(
+ nodes, p=np.array(magnitudes, dtype='float32') / np.sum(magnitudes))
+
+
+class StatementSampler(NodeSampler):
+ sample_map = dict((
+ (gast.Assign, 10),
+ (gast.Print, 1),
+ (gast.If, 2),
+ (gast.While, 2),
+ (gast.For, 0),
+ ))
+
+
+class ExpressionSampler(NodeSampler):
+ sample_map = dict((
+ (gast.UnaryOp, 1),
+ (gast.BinOp, 8),
+ (gast.Name, 1),
+ (gast.Call, 0),
+ ))
+
+
+class CompareSampler(NodeSampler):
+ sample_map = dict((
+ (gast.Eq, 1),
+ (gast.NotEq, 1),
+ (gast.Lt, 1),
+ (gast.LtE, 1),
+ (gast.Gt, 1),
+ (gast.GtE, 1),
+ (gast.Is, 1),
+ (gast.IsNot, 1),
+ ))
+
+
+class BinaryOpSampler(NodeSampler):
+ sample_map = dict((
+ (gast.Add, 1),
+ (gast.Sub, 1),
+ (gast.Mult, 1),
+ (gast.Div, 1),
+ (gast.FloorDiv, 1),
+ (gast.Mod, 1),
+ (gast.Pow, 1),
+ ))
+
+
+class UnaryOpSampler(NodeSampler):
+ sample_map = dict(((gast.USub, 1), (gast.UAdd, 0)))
+
+
+class NameSampler(NodeSampler):
+ sample_map = dict((
+ ('new', 1),
+ ('existing', 1),
+ ))
+
+
+N_CONTROLFLOW_STATEMENTS = 10
+N_FUNCTIONDEF_STATEMENTS = 10
+
+
+class CodeGenerator(object):
+ """Generate random syntactically-valid Python ASTs."""
+
+ def __init__(self, max_depth=3, depth=0):
+ self.max_depth = max_depth
+ self.depth = depth
+
+ def generate_statement(self):
+ """Generate a statement node, dispatching to the correct class method."""
+ desired_node = StatementSampler().sample()
+ self.depth += 1
+
+ # Enforce some constraints on generating statements.
+ # E.g., if statements need at least 3 readable variables.
+ # If we fail to satisfy our constraints, draw another sample.
+ if desired_node in (gast.While, gast.For, gast.If):
+ if self.depth > self.max_depth:
+ return self.generate_statement()
+
+ # Go get the generator method and run it
+ method = 'generate_' + desired_node.__name__
+ visitor = getattr(self, method)
+ node = visitor()
+ self.depth -= 1
+ return node
+
+ def sample_node_list(self, low, high, generator):
+ """Generate a list of statements of random length.
+
+ Args:
+ low: Fewest number of statements to generate.
+ high: Highest number of statements to generate.
+ generator: Function to call to generate nodes.
+
+ Returns:
+ A list of statements.
+ """
+ statements = []
+ for _ in range(np.random.randint(low, high)):
+ statements.append(generator())
+ return statements
+
+ def generate_Name(self, ctx=gast.Load()):
+ variable_name = '_' + ''.join(
+ random.choice(string.ascii_lowercase) for _ in range(4))
+ return gast.Name(variable_name, ctx=ctx, annotation=None)
+
+ def generate_BinOp(self):
+ # TODO(alexbw): convert to generate_expression when we get to limit
+ # expression depth.
+ op = BinaryOpSampler().sample()()
+ return gast.BinOp(self.generate_Name(), op, self.generate_Name())
+
+ def generate_Compare(self):
+ op = CompareSampler().sample()()
+ return gast.Compare(self.generate_Name(), [op], [self.generate_Name()])
+
+ def generate_UnaryOp(self):
+ operand = self.generate_Name()
+ op = UnaryOpSampler().sample()()
+ return gast.UnaryOp(op, operand)
+
+ def generate_expression(self):
+ desired_node = ExpressionSampler().sample()
+ # Go get the generator method and run it
+ method = 'generate_' + desired_node.__name__
+ generator = getattr(self, method)
+ return generator()
+
+ def generate_Assign(self):
+ """Generate an Assign node."""
+ # Generate left-hand side
+ target_node = self.generate_Name(gast.Store())
+ # Generate right-hand side
+ value_node = self.generate_expression()
+ # Put it all together
+ node = gast.Assign(targets=[target_node], value=value_node)
+ return node
+
+ def generate_If(self):
+ """Generate an If node."""
+ test = self.generate_Compare()
+
+ # Generate true branch statements
+ body = self.sample_node_list(
+ low=1,
+ high=N_CONTROLFLOW_STATEMENTS // 2,
+ generator=self.generate_statement)
+
+ # Generate false branch statements
+ orelse = self.sample_node_list(
+ low=1,
+ high=N_CONTROLFLOW_STATEMENTS // 2,
+ generator=self.generate_statement)
+
+ node = gast.If(test, body, orelse)
+ return node
+
+ def generate_While(self):
+ """Generate a While node."""
+
+ test = self.generate_Compare()
+ body = self.sample_node_list(
+ low=1, high=N_CONTROLFLOW_STATEMENTS, generator=self.generate_statement)
+ orelse = [] # not generating else statements
+
+ node = gast.While(test, body, orelse)
+ return node
+
+ def generate_Call(self):
+ raise NotImplementedError
+
+ def generate_Return(self):
+ return gast.Return(self.generate_expression())
+
+ def generate_Print(self):
+ return templates.replace('print(x)', x=self.generate_expression())[0]
+
+ def generate_FunctionDef(self):
+ """Generate a FunctionDef node."""
+
+ # Generate the arguments, register them as available
+ arg_vars = self.sample_node_list(
+ low=2, high=10, generator=lambda: self.generate_Name(gast.Param()))
+ args = gast.arguments(arg_vars, None, [], [], None, [])
+
+ # Generate the function body
+ body = self.sample_node_list(
+ low=1, high=N_FUNCTIONDEF_STATEMENTS, generator=self.generate_statement)
+ body.append(self.generate_Return())
+ fn_name = self.generate_Name().id
+ node = gast.FunctionDef(fn_name, args, body, (), None)
+ return node
+
+
+def generate_random_functiondef():
+ return CodeGenerator().generate_FunctionDef()
diff --git a/tensorflow/contrib/autograph/pyct/testing/codegen_test.py b/tensorflow/contrib/autograph/pyct/testing/codegen_test.py
new file mode 100644
index 0000000000..255c3b2a2e
--- /dev/null
+++ b/tensorflow/contrib/autograph/pyct/testing/codegen_test.py
@@ -0,0 +1,40 @@
+# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Tests for type_info module."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import numpy as np
+
+from tensorflow.contrib.autograph.pyct import compiler
+from tensorflow.contrib.autograph.pyct.testing import codegen
+from tensorflow.python.platform import test
+
+
+class CodeGenTest(test.TestCase):
+
+ def test_codegen_gens(self):
+ np.random.seed(0)
+ for _ in range(1000):
+ node = codegen.generate_random_functiondef()
+ fn = compiler.ast_to_object(node)
+ self.assertIsNotNone(
+ fn, 'Generated invalid AST that could not convert to source.')
+
+
+if __name__ == '__main__':
+ test.main()
diff --git a/tensorflow/contrib/autograph/pyct/transformer.py b/tensorflow/contrib/autograph/pyct/transformer.py
index 3e8906823e..969ca12244 100644
--- a/tensorflow/contrib/autograph/pyct/transformer.py
+++ b/tensorflow/contrib/autograph/pyct/transformer.py
@@ -421,10 +421,13 @@ class Base(gast.NodeTransformer):
source_file = self.entity_info.source_file
did_enter_function = False
local_scope_size_at_entry = len(self._local_scope_state)
+ processing_expr_node = False
try:
if isinstance(node, (gast.FunctionDef, gast.ClassDef, gast.Lambda)):
did_enter_function = True
+ elif isinstance(node, gast.Expr):
+ processing_expr_node = True
if did_enter_function:
self._enclosing_entities.append(node)
@@ -433,9 +436,23 @@ class Base(gast.NodeTransformer):
self._lineno = node.lineno
self._col_offset = node.col_offset
+ if processing_expr_node:
+ entry_expr_value = node.value
+
if not anno.hasanno(node, anno.Basic.SKIP_PROCESSING):
result = super(Base, self).visit(node)
+ # Adjust for consistency: replacing the value of an Expr with
+ # an Assign node removes the need for the Expr node.
+ if processing_expr_node:
+ if isinstance(result, gast.Expr) and result.value != entry_expr_value:
+ # When the replacement is a list, it is assumed that the list came
+ # from a template that contained a number of statements, which
+ # themselves are standalone and don't require an enclosing Expr.
+ if isinstance(result.value,
+ (list, tuple, gast.Assign, gast.AugAssign)):
+ result = result.value
+
# On exception, the local scope integrity is not guaranteed.
if did_enter_function:
self._enclosing_entities.pop()
diff --git a/tensorflow/contrib/autograph/utils/builtins.py b/tensorflow/contrib/autograph/utils/builtins.py
index 71079cfdc0..ccbe5fc954 100644
--- a/tensorflow/contrib/autograph/utils/builtins.py
+++ b/tensorflow/contrib/autograph/utils/builtins.py
@@ -27,6 +27,7 @@ from tensorflow.contrib.autograph.utils import type_check
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import tensor_util
from tensorflow.python.ops import array_ops
+from tensorflow.python.ops import list_ops
from tensorflow.python.ops import logging_ops
from tensorflow.python.ops import math_ops
@@ -50,7 +51,9 @@ def dynamic_builtin(f, *args, **kwargs):
def dynamic_len(list_or_tensor):
"""Implementation of len using dynamic dispatch."""
- if tensor_util.is_tensor(list_or_tensor):
+ if _is_tensor_list(list_or_tensor):
+ return list_ops.tensor_list_length(list_or_tensor)
+ elif tensor_util.is_tensor(list_or_tensor):
shape = list_or_tensor.shape
if not shape.ndims:
raise ValueError(
@@ -59,6 +62,11 @@ def dynamic_len(list_or_tensor):
return len(list_or_tensor)
+def _is_tensor_list(list_or_tensor):
+ return (tensor_util.is_tensor(list_or_tensor)
+ and list_or_tensor.dtype == dtypes.variant)
+
+
def dynamic_int(num_or_tensor, **kwargs):
"""Implementation of int() using dynamic dispatch."""
if tensor_util.is_tensor(num_or_tensor):
diff --git a/tensorflow/contrib/boosted_trees/estimator_batch/BUILD b/tensorflow/contrib/boosted_trees/estimator_batch/BUILD
index ef0e80cd09..f4a375328e 100644
--- a/tensorflow/contrib/boosted_trees/estimator_batch/BUILD
+++ b/tensorflow/contrib/boosted_trees/estimator_batch/BUILD
@@ -147,6 +147,7 @@ py_library(
deps = [
":distillation_loss",
":estimator_utils",
+ ":model",
":trainer_hooks",
"//tensorflow/contrib/boosted_trees:gbdt_batch",
"//tensorflow/contrib/boosted_trees:model_ops_py",
diff --git a/tensorflow/contrib/boosted_trees/estimator_batch/dnn_tree_combined_estimator.py b/tensorflow/contrib/boosted_trees/estimator_batch/dnn_tree_combined_estimator.py
index 7eb429b636..dbfa69edcb 100644
--- a/tensorflow/contrib/boosted_trees/estimator_batch/dnn_tree_combined_estimator.py
+++ b/tensorflow/contrib/boosted_trees/estimator_batch/dnn_tree_combined_estimator.py
@@ -26,6 +26,7 @@ from __future__ import print_function
import six
from tensorflow.contrib import layers
+from tensorflow.contrib.boosted_trees.estimator_batch import model
from tensorflow.contrib.boosted_trees.estimator_batch import distillation_loss
from tensorflow.contrib.boosted_trees.estimator_batch import estimator_utils
from tensorflow.contrib.boosted_trees.estimator_batch import trainer_hooks
@@ -34,6 +35,7 @@ from tensorflow.contrib.boosted_trees.python.training.functions import gbdt_batc
from tensorflow.contrib.layers.python.layers import optimizers
from tensorflow.contrib.learn.python.learn.estimators import estimator
from tensorflow.contrib.learn.python.learn.estimators import head as head_lib
+from tensorflow.python.estimator import estimator as core_estimator
from tensorflow.contrib.learn.python.learn.estimators import model_fn
from tensorflow.python.feature_column import feature_column as feature_column_lib
from tensorflow.python.framework import ops
@@ -62,27 +64,29 @@ def _add_hidden_layer_summary(value, tag):
summary.histogram("%s_activation" % tag, value)
-def _dnn_tree_combined_model_fn(features,
- labels,
- mode,
- head,
- dnn_hidden_units,
- dnn_feature_columns,
- tree_learner_config,
- num_trees,
- tree_examples_per_layer,
- config=None,
- dnn_optimizer="Adagrad",
- dnn_activation_fn=nn.relu,
- dnn_dropout=None,
- dnn_input_layer_partitioner=None,
- dnn_input_layer_to_tree=True,
- dnn_steps_to_train=10000,
- predict_with_tree_only=False,
- tree_feature_columns=None,
- tree_center_bias=False,
- dnn_to_tree_distillation_param=None,
- use_core_versions=False):
+def _dnn_tree_combined_model_fn(
+ features,
+ labels,
+ mode,
+ head,
+ dnn_hidden_units,
+ dnn_feature_columns,
+ tree_learner_config,
+ num_trees,
+ tree_examples_per_layer,
+ config=None,
+ dnn_optimizer="Adagrad",
+ dnn_activation_fn=nn.relu,
+ dnn_dropout=None,
+ dnn_input_layer_partitioner=None,
+ dnn_input_layer_to_tree=True,
+ dnn_steps_to_train=10000,
+ predict_with_tree_only=False,
+ tree_feature_columns=None,
+ tree_center_bias=False,
+ dnn_to_tree_distillation_param=None,
+ use_core_versions=False,
+ output_type=model.ModelBuilderOutputType.MODEL_FN_OPS):
"""DNN and GBDT combined model_fn.
Args:
@@ -156,6 +160,10 @@ def _dnn_tree_combined_model_fn(features,
partitioned_variables.min_max_variable_partitioner(
max_partitions=config.num_ps_replicas, min_slice_size=64 << 20))
+ if (output_type == model.ModelBuilderOutputType.ESTIMATOR_SPEC and
+ not use_core_versions):
+ raise ValueError("You must use core versions with Estimator Spec")
+
with variable_scope.variable_scope(
dnn_parent_scope,
values=tuple(six.itervalues(features)),
@@ -235,7 +243,8 @@ def _dnn_tree_combined_model_fn(features,
learner_config=tree_learner_config,
feature_columns=tree_feature_columns,
logits_dimension=head.logits_dimension,
- features=tree_features)
+ features=tree_features,
+ use_core_columns=use_core_versions)
with ops.name_scope("gbdt"):
predictions_dict = gbdt_model.predict(mode)
@@ -284,63 +293,96 @@ def _dnn_tree_combined_model_fn(features,
del loss
return control_flow_ops.no_op()
- if use_core_versions:
- model_fn_ops = head.create_estimator_spec(
- features=features,
- mode=mode,
- labels=labels,
- train_op_fn=_no_train_op_fn,
- logits=tree_train_logits)
- dnn_train_op = head.create_estimator_spec(
- features=features,
- mode=mode,
- labels=labels,
- train_op_fn=_dnn_train_op_fn,
- logits=dnn_logits)
- dnn_train_op = estimator_utils.estimator_spec_to_model_fn_ops(
- dnn_train_op).train_op
+ if tree_center_bias:
+ num_trees += 1
+ finalized_trees, attempted_trees = gbdt_model.get_number_of_trees_tensor()
- tree_train_op = head.create_estimator_spec(
- features=tree_features,
- mode=mode,
- labels=labels,
- train_op_fn=_tree_train_op_fn,
- logits=tree_train_logits)
- tree_train_op = estimator_utils.estimator_spec_to_model_fn_ops(
- tree_train_op).train_op
+ if output_type == model.ModelBuilderOutputType.MODEL_FN_OPS:
+ if use_core_versions:
+ model_fn_ops = head.create_estimator_spec(
+ features=features,
+ mode=mode,
+ labels=labels,
+ train_op_fn=_no_train_op_fn,
+ logits=tree_train_logits)
+ dnn_train_op = head.create_estimator_spec(
+ features=features,
+ mode=mode,
+ labels=labels,
+ train_op_fn=_dnn_train_op_fn,
+ logits=dnn_logits)
+ dnn_train_op = estimator_utils.estimator_spec_to_model_fn_ops(
+ dnn_train_op).train_op
- model_fn_ops = estimator_utils.estimator_spec_to_model_fn_ops(model_fn_ops)
- else:
- model_fn_ops = head.create_model_fn_ops(
+ tree_train_op = head.create_estimator_spec(
+ features=tree_features,
+ mode=mode,
+ labels=labels,
+ train_op_fn=_tree_train_op_fn,
+ logits=tree_train_logits)
+ tree_train_op = estimator_utils.estimator_spec_to_model_fn_ops(
+ tree_train_op).train_op
+
+ model_fn_ops = estimator_utils.estimator_spec_to_model_fn_ops(
+ model_fn_ops)
+ else:
+ model_fn_ops = head.create_model_fn_ops(
+ features=features,
+ mode=mode,
+ labels=labels,
+ train_op_fn=_no_train_op_fn,
+ logits=tree_train_logits)
+ dnn_train_op = head.create_model_fn_ops(
+ features=features,
+ mode=mode,
+ labels=labels,
+ train_op_fn=_dnn_train_op_fn,
+ logits=dnn_logits).train_op
+ tree_train_op = head.create_model_fn_ops(
+ features=tree_features,
+ mode=mode,
+ labels=labels,
+ train_op_fn=_tree_train_op_fn,
+ logits=tree_train_logits).train_op
+
+ # Add the hooks
+ model_fn_ops.training_hooks.extend([
+ trainer_hooks.SwitchTrainOp(dnn_train_op, dnn_steps_to_train,
+ tree_train_op),
+ trainer_hooks.StopAfterNTrees(num_trees, attempted_trees,
+ finalized_trees)
+ ])
+ return model_fn_ops
+
+ elif output_type == model.ModelBuilderOutputType.ESTIMATOR_SPEC:
+ fusion_spec = head.create_estimator_spec(
features=features,
mode=mode,
labels=labels,
train_op_fn=_no_train_op_fn,
logits=tree_train_logits)
- dnn_train_op = head.create_model_fn_ops(
+ dnn_spec = head.create_estimator_spec(
features=features,
mode=mode,
labels=labels,
train_op_fn=_dnn_train_op_fn,
- logits=dnn_logits).train_op
- tree_train_op = head.create_model_fn_ops(
+ logits=dnn_logits)
+ tree_spec = head.create_estimator_spec(
features=tree_features,
mode=mode,
labels=labels,
train_op_fn=_tree_train_op_fn,
- logits=tree_train_logits).train_op
-
- if tree_center_bias:
- num_trees += 1
- finalized_trees, attempted_trees = gbdt_model.get_number_of_trees_tensor()
-
- model_fn_ops.training_hooks.extend([
- trainer_hooks.SwitchTrainOp(dnn_train_op, dnn_steps_to_train,
- tree_train_op),
- trainer_hooks.StopAfterNTrees(num_trees, attempted_trees, finalized_trees)
- ])
+ logits=tree_train_logits)
- return model_fn_ops
+ training_hooks = [
+ trainer_hooks.SwitchTrainOp(dnn_spec.train_op, dnn_steps_to_train,
+ tree_spec.train_op),
+ trainer_hooks.StopAfterNTrees(num_trees, attempted_trees,
+ finalized_trees)
+ ]
+ fusion_spec = fusion_spec._replace(training_hooks=training_hooks +
+ list(fusion_spec.training_hooks))
+ return fusion_spec
class DNNBoostedTreeCombinedClassifier(estimator.Estimator):
@@ -697,3 +739,100 @@ class DNNBoostedTreeCombinedEstimator(estimator.Estimator):
model_dir=model_dir,
config=config,
feature_engineering_fn=feature_engineering_fn)
+
+
+class CoreDNNBoostedTreeCombinedEstimator(core_estimator.Estimator):
+ """Initializes a core version of DNNBoostedTreeCombinedEstimator.
+
+ Args:
+ dnn_hidden_units: List of hidden units per layer for DNN.
+ dnn_feature_columns: An iterable containing all the feature columns
+ used by the model's DNN.
+ tree_learner_config: A config for the tree learner.
+ num_trees: Number of trees to grow model to after training DNN.
+ tree_examples_per_layer: Number of examples to accumulate before
+ growing the tree a layer. This value has a big impact on model
+ quality and should be set equal to the number of examples in
+ training dataset if possible. It can also be a function that computes
+ the number of examples based on the depth of the layer that's
+ being built.
+ head: `Head` instance.
+ model_dir: Directory for model exports.
+ config: `RunConfig` of the estimator.
+ dnn_optimizer: string, `Optimizer` object, or callable that defines the
+ optimizer to use for training the DNN. If `None`, will use the Adagrad
+ optimizer with default learning rate.
+ dnn_activation_fn: Activation function applied to each layer of the DNN.
+ If `None`, will use `tf.nn.relu`.
+ dnn_dropout: When not `None`, the probability to drop out a given
+ unit in the DNN.
+ dnn_input_layer_partitioner: Partitioner for input layer of the DNN.
+ Defaults to `min_max_variable_partitioner` with `min_slice_size`
+ 64 << 20.
+ dnn_input_layer_to_tree: Whether to provide the DNN's input layer
+ as a feature to the tree.
+ dnn_steps_to_train: Number of steps to train dnn for before switching
+ to gbdt.
+ predict_with_tree_only: Whether to use only the tree model output as the
+ final prediction.
+ tree_feature_columns: An iterable containing all the feature columns
+ used by the model's boosted trees. If dnn_input_layer_to_tree is
+ set to True, these features are in addition to dnn_feature_columns.
+ tree_center_bias: Whether a separate tree should be created for
+ first fitting the bias.
+ dnn_to_tree_distillation_param: A Tuple of (float, loss_fn), where the
+ float defines the weight of the distillation loss, and the loss_fn, for
+ computing distillation loss, takes dnn_logits, tree_logits and weight
+ tensor. If the entire tuple is None, no distillation will be applied. If
+ only the loss_fn is None, we will take the sigmoid/softmax cross entropy
+ loss be default. When distillation is applied, `predict_with_tree_only`
+ will be set to True.
+ """
+
+ def __init__(self,
+ dnn_hidden_units,
+ dnn_feature_columns,
+ tree_learner_config,
+ num_trees,
+ tree_examples_per_layer,
+ head,
+ model_dir=None,
+ config=None,
+ dnn_optimizer="Adagrad",
+ dnn_activation_fn=nn.relu,
+ dnn_dropout=None,
+ dnn_input_layer_partitioner=None,
+ dnn_input_layer_to_tree=True,
+ dnn_steps_to_train=10000,
+ predict_with_tree_only=False,
+ tree_feature_columns=None,
+ tree_center_bias=False,
+ dnn_to_tree_distillation_param=None):
+
+ def _model_fn(features, labels, mode, config):
+ return _dnn_tree_combined_model_fn(
+ features=features,
+ labels=labels,
+ mode=mode,
+ head=head,
+ dnn_hidden_units=dnn_hidden_units,
+ dnn_feature_columns=dnn_feature_columns,
+ tree_learner_config=tree_learner_config,
+ num_trees=num_trees,
+ tree_examples_per_layer=tree_examples_per_layer,
+ config=config,
+ dnn_optimizer=dnn_optimizer,
+ dnn_activation_fn=dnn_activation_fn,
+ dnn_dropout=dnn_dropout,
+ dnn_input_layer_partitioner=dnn_input_layer_partitioner,
+ dnn_input_layer_to_tree=dnn_input_layer_to_tree,
+ dnn_steps_to_train=dnn_steps_to_train,
+ predict_with_tree_only=predict_with_tree_only,
+ tree_feature_columns=tree_feature_columns,
+ tree_center_bias=tree_center_bias,
+ dnn_to_tree_distillation_param=dnn_to_tree_distillation_param,
+ output_type=model.ModelBuilderOutputType.ESTIMATOR_SPEC,
+ use_core_versions=True)
+
+ super(CoreDNNBoostedTreeCombinedEstimator, self).__init__(
+ model_fn=_model_fn, model_dir=model_dir, config=config)
diff --git a/tensorflow/contrib/boosted_trees/estimator_batch/dnn_tree_combined_estimator_test.py b/tensorflow/contrib/boosted_trees/estimator_batch/dnn_tree_combined_estimator_test.py
index 9b7acfa664..839eedd3a8 100644
--- a/tensorflow/contrib/boosted_trees/estimator_batch/dnn_tree_combined_estimator_test.py
+++ b/tensorflow/contrib/boosted_trees/estimator_batch/dnn_tree_combined_estimator_test.py
@@ -28,10 +28,11 @@ from tensorflow.python.estimator.canned import head as head_lib
from tensorflow.python.feature_column import feature_column_lib as core_feature_column
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import dtypes
+from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops.losses import losses
from tensorflow.python.platform import googletest
-
+from tensorflow.python.training import checkpoint_utils
def _train_input_fn():
features = {
@@ -156,5 +157,72 @@ class DNNBoostedTreeCombinedTest(test_util.TensorFlowTestCase):
classifier.evaluate(input_fn=_eval_input_fn, steps=1)
+class CoreDNNBoostedTreeCombinedTest(test_util.TensorFlowTestCase):
+
+ def _assert_checkpoint(self, model_dir, global_step):
+ reader = checkpoint_utils.load_checkpoint(model_dir)
+ self.assertEqual(global_step, reader.get_tensor(ops.GraphKeys.GLOBAL_STEP))
+
+ def testTrainEvaluateInferDoesNotThrowErrorWithNoDnnInput(self):
+ head_fn = head_lib._binary_logistic_head_with_sigmoid_cross_entropy_loss(
+ loss_reduction=losses.Reduction.SUM_OVER_NONZERO_WEIGHTS)
+
+ learner_config = learner_pb2.LearnerConfig()
+ learner_config.num_classes = 2
+ learner_config.constraints.max_tree_depth = 3
+ model_dir = tempfile.mkdtemp()
+ config = run_config.RunConfig()
+
+ est = estimator.CoreDNNBoostedTreeCombinedEstimator(
+ head=head_fn,
+ dnn_hidden_units=[1],
+ dnn_feature_columns=[core_feature_column.numeric_column("x")],
+ tree_learner_config=learner_config,
+ num_trees=1,
+ tree_examples_per_layer=3,
+ model_dir=model_dir,
+ config=config,
+ dnn_steps_to_train=10,
+ dnn_input_layer_to_tree=False,
+ tree_feature_columns=[core_feature_column.numeric_column("x")])
+
+ # Train for a few steps.
+ est.train(input_fn=_train_input_fn, steps=1000)
+ # 10 steps for dnn, 3 for 1 tree of depth 3 + 1 after the tree finished
+ self._assert_checkpoint(est.model_dir, global_step=14)
+ res = est.evaluate(input_fn=_eval_input_fn, steps=1)
+ self.assertLess(0.5, res["auc"])
+ est.predict(input_fn=_eval_input_fn)
+
+ def testTrainEvaluateInferDoesNotThrowErrorWithDnnInput(self):
+ head_fn = head_lib._binary_logistic_head_with_sigmoid_cross_entropy_loss(
+ loss_reduction=losses.Reduction.SUM_OVER_NONZERO_WEIGHTS)
+
+ learner_config = learner_pb2.LearnerConfig()
+ learner_config.num_classes = 2
+ learner_config.constraints.max_tree_depth = 3
+ model_dir = tempfile.mkdtemp()
+ config = run_config.RunConfig()
+
+ est = estimator.CoreDNNBoostedTreeCombinedEstimator(
+ head=head_fn,
+ dnn_hidden_units=[1],
+ dnn_feature_columns=[core_feature_column.numeric_column("x")],
+ tree_learner_config=learner_config,
+ num_trees=1,
+ tree_examples_per_layer=3,
+ model_dir=model_dir,
+ config=config,
+ dnn_steps_to_train=10,
+ dnn_input_layer_to_tree=True,
+ tree_feature_columns=[])
+
+ # Train for a few steps.
+ est.train(input_fn=_train_input_fn, steps=1000)
+ res = est.evaluate(input_fn=_eval_input_fn, steps=1)
+ self.assertLess(0.5, res["auc"])
+ est.predict(input_fn=_eval_input_fn)
+
+
if __name__ == "__main__":
googletest.main()
diff --git a/tensorflow/contrib/boosted_trees/estimator_batch/estimator.py b/tensorflow/contrib/boosted_trees/estimator_batch/estimator.py
index 38fa8c3834..2df879f924 100644
--- a/tensorflow/contrib/boosted_trees/estimator_batch/estimator.py
+++ b/tensorflow/contrib/boosted_trees/estimator_batch/estimator.py
@@ -26,6 +26,12 @@ from tensorflow.python.estimator import estimator as core_estimator
from tensorflow.python.ops import math_ops
+# ================== Old estimator interface===================================
+# The estimators below were designed for old feature columns and old estimator
+# interface. They can be used with new feature columns and losses by setting
+# use_core_libs = True.
+
+
class GradientBoostedDecisionTreeClassifier(estimator.Estimator):
"""An estimator using gradient boosted decision trees."""
@@ -356,9 +362,16 @@ class GradientBoostedDecisionTreeRanker(estimator.Estimator):
config=config,
feature_engineering_fn=feature_engineering_fn)
+# ================== New Estimator interface===================================
+# The estimators below use new core Estimator interface and must be used with
+# new feature columns and heads.
+
class CoreGradientBoostedDecisionTreeEstimator(core_estimator.Estimator):
- """An estimator using gradient boosted decision trees."""
+ """An estimator using gradient boosted decision trees.
+
+ Useful for training with user specified `Head`.
+ """
def __init__(self,
learner_config,
@@ -374,6 +387,36 @@ class CoreGradientBoostedDecisionTreeEstimator(core_estimator.Estimator):
logits_modifier_function=None,
center_bias=True,
output_leaf_index=False):
+ """Initializes a core version of GradientBoostedDecisionTreeEstimator.
+
+ Args:
+ learner_config: A config for the learner.
+ examples_per_layer: Number of examples to accumulate before growing a
+ layer. It can also be a function that computes the number of examples
+ based on the depth of the layer that's being built.
+ head: `Head` instance.
+ num_trees: An int, number of trees to build.
+ feature_columns: A list of feature columns.
+ weight_column_name: Name of the column for weights, or None if not
+ weighted.
+ model_dir: Directory for model exports, etc.
+ config: `RunConfig` object to configure the runtime settings.
+ label_keys: Optional list of strings with size `[n_classes]` defining the
+ label vocabulary. Only supported for `n_classes` > 2.
+ feature_engineering_fn: Feature engineering function. Takes features and
+ labels which are the output of `input_fn` and returns features and
+ labels which will be fed into the model.
+ logits_modifier_function: A modifier function for the logits.
+ center_bias: Whether a separate tree should be created for first fitting
+ the bias.
+ output_leaf_index: whether to output leaf indices along with predictions
+ during inference. The leaf node indexes are available in predictions
+ dict by the key 'leaf_index'. For example,
+ result_dict = classifier.predict(...)
+ for example_prediction_result in result_dict:
+ # access leaf index list by example_prediction_result["leaf_index"]
+ # which contains one leaf index per tree
+ """
def _model_fn(features, labels, mode, config):
return model.model_builder(
@@ -397,3 +440,87 @@ class CoreGradientBoostedDecisionTreeEstimator(core_estimator.Estimator):
super(CoreGradientBoostedDecisionTreeEstimator, self).__init__(
model_fn=_model_fn, model_dir=model_dir, config=config)
+
+
+class CoreGradientBoostedDecisionTreeRanker(core_estimator.Estimator):
+ """A ranking estimator using gradient boosted decision trees."""
+
+ def __init__(
+ self,
+ learner_config,
+ examples_per_layer,
+ head,
+ ranking_model_pair_keys,
+ num_trees=None,
+ feature_columns=None,
+ weight_column_name=None,
+ model_dir=None,
+ config=None,
+ label_keys=None,
+ logits_modifier_function=None,
+ center_bias=False,
+ output_leaf_index=False,
+ ):
+ """Initializes a GradientBoostedDecisionTreeRanker instance.
+
+ This is an estimator that can be trained off the pairwise data and can be
+ used for inference on non-paired data. This is essentially LambdaMart.
+ Args:
+ learner_config: A config for the learner.
+ examples_per_layer: Number of examples to accumulate before growing a
+ layer. It can also be a function that computes the number of examples
+ based on the depth of the layer that's being built.
+ head: `Head` instance.
+ ranking_model_pair_keys: Keys to distinguish between features
+ for left and right part of the training pairs for ranking. For example,
+ for an Example with features "a.f1" and "b.f1", the keys would be
+ ("a", "b").
+ num_trees: An int, number of trees to build.
+ feature_columns: A list of feature columns.
+ weight_column_name: Name of the column for weights, or None if not
+ weighted.
+ model_dir: Directory for model exports, etc.
+ config: `RunConfig` object to configure the runtime settings.
+ label_keys: Optional list of strings with size `[n_classes]` defining the
+ label vocabulary. Only supported for `n_classes` > 2.
+ logits_modifier_function: A modifier function for the logits.
+ center_bias: Whether a separate tree should be created for first fitting
+ the bias.
+ output_leaf_index: whether to output leaf indices along with predictions
+ during inference. The leaf node indexes are available in predictions
+ dict by the key 'leaf_index'. It is a Tensor of rank 2 and its shape is
+ [batch_size, num_trees].
+ For example,
+ result_iter = classifier.predict(...)
+ for result_dict in result_iter:
+ # access leaf index list by result_dict["leaf_index"]
+ # which contains one leaf index per tree
+
+ Raises:
+ ValueError: If learner_config is not valid.
+ """
+
+ def _model_fn(features, labels, mode, config):
+ return model.ranking_model_builder(
+ features=features,
+ labels=labels,
+ mode=mode,
+ config=config,
+ params={
+ 'head': head,
+ 'n_classes': 2,
+ 'feature_columns': feature_columns,
+ 'learner_config': learner_config,
+ 'num_trees': num_trees,
+ 'weight_column_name': weight_column_name,
+ 'examples_per_layer': examples_per_layer,
+ 'center_bias': center_bias,
+ 'logits_modifier_function': logits_modifier_function,
+ 'use_core_libs': True,
+ 'output_leaf_index': output_leaf_index,
+ 'ranking_model_pair_keys': ranking_model_pair_keys,
+ },
+ output_type=model.ModelBuilderOutputType.ESTIMATOR_SPEC)
+
+ super(CoreGradientBoostedDecisionTreeRanker, self).__init__(
+ model_fn=_model_fn, model_dir=model_dir, config=config)
diff --git a/tensorflow/contrib/boosted_trees/estimator_batch/estimator_test.py b/tensorflow/contrib/boosted_trees/estimator_batch/estimator_test.py
index f787d3cdb8..9e9febbbef 100644
--- a/tensorflow/contrib/boosted_trees/estimator_batch/estimator_test.py
+++ b/tensorflow/contrib/boosted_trees/estimator_batch/estimator_test.py
@@ -203,7 +203,7 @@ class BoostedTreeEstimatorTest(test_util.TensorFlowTestCase):
model.predict(input_fn=_infer_ranking_train_input_fn)
-class CoreGradientBoostedDecisionTreeEstimator(test_util.TensorFlowTestCase):
+class CoreGradientBoostedDecisionTreeEstimators(test_util.TensorFlowTestCase):
def testTrainEvaluateInferDoesNotThrowError(self):
head_fn = head_lib._binary_logistic_head_with_sigmoid_cross_entropy_loss(
@@ -229,6 +229,34 @@ class CoreGradientBoostedDecisionTreeEstimator(test_util.TensorFlowTestCase):
est.evaluate(input_fn=_eval_input_fn, steps=1)
est.predict(input_fn=_eval_input_fn)
+ def testRankingDontThrowExceptionForForEstimator(self):
+ learner_config = learner_pb2.LearnerConfig()
+ learner_config.num_classes = 2
+ learner_config.constraints.max_tree_depth = 1
+ model_dir = tempfile.mkdtemp()
+ config = run_config.RunConfig()
+
+ head_fn = head_lib._binary_logistic_head_with_sigmoid_cross_entropy_loss(
+ loss_reduction=losses.Reduction.SUM_OVER_NONZERO_WEIGHTS)
+
+ est = estimator.CoreGradientBoostedDecisionTreeRanker(
+ head=head_fn,
+ learner_config=learner_config,
+ num_trees=1,
+ examples_per_layer=3,
+ model_dir=model_dir,
+ config=config,
+ feature_columns=[
+ core_feature_column.numeric_column("f1"),
+ core_feature_column.numeric_column("f2")
+ ],
+ ranking_model_pair_keys=("a", "b"))
+
+ # Train for a few steps.
+ est.train(input_fn=_ranking_train_input_fn, steps=1000)
+ est.evaluate(input_fn=_ranking_train_input_fn, steps=1)
+ est.predict(input_fn=_infer_ranking_train_input_fn)
+
if __name__ == "__main__":
googletest.main()
diff --git a/tensorflow/contrib/boosted_trees/estimator_batch/model.py b/tensorflow/contrib/boosted_trees/estimator_batch/model.py
index 2fbe72951a..161cc42cb0 100644
--- a/tensorflow/contrib/boosted_trees/estimator_batch/model.py
+++ b/tensorflow/contrib/boosted_trees/estimator_batch/model.py
@@ -59,6 +59,8 @@ def model_builder(features,
* center_bias: Whether a separate tree should be created for first fitting
the bias.
config: `RunConfig` of the estimator.
+ output_type: Whether to return ModelFnOps (old interface) or EstimatorSpec
+ (new interface).
Returns:
A `ModelFnOps` object.
@@ -126,14 +128,15 @@ def model_builder(features,
create_estimator_spec_op = getattr(head, "create_estimator_spec", None)
+ training_hooks = []
if num_trees:
if center_bias:
num_trees += 1
+
finalized_trees, attempted_trees = gbdt_model.get_number_of_trees_tensor()
- training_hooks = [
+ training_hooks.append(
trainer_hooks.StopAfterNTrees(num_trees, attempted_trees,
- finalized_trees)
- ]
+ finalized_trees))
if output_type == ModelBuilderOutputType.MODEL_FN_OPS:
if use_core_libs and callable(create_estimator_spec_op):
@@ -175,7 +178,12 @@ def model_builder(features,
return model_fn_ops
-def ranking_model_builder(features, labels, mode, params, config):
+def ranking_model_builder(features,
+ labels,
+ mode,
+ params,
+ config,
+ output_type=ModelBuilderOutputType.MODEL_FN_OPS):
"""Multi-machine batch gradient descent tree model for ranking.
Args:
@@ -199,6 +207,9 @@ def ranking_model_builder(features, labels, mode, params, config):
for an Example with features "a.f1" and "b.f1", the keys would be
("a", "b").
config: `RunConfig` of the estimator.
+ output_type: Whether to return ModelFnOps (old interface) or EstimatorSpec
+ (new interface).
+
Returns:
A `ModelFnOps` object.
@@ -326,31 +337,54 @@ def ranking_model_builder(features, labels, mode, params, config):
return update_op
create_estimator_spec_op = getattr(head, "create_estimator_spec", None)
- if use_core_libs and callable(create_estimator_spec_op):
- model_fn_ops = head.create_estimator_spec(
- features=features,
- mode=mode,
- labels=labels,
- train_op_fn=_train_op_fn,
- logits=logits)
- model_fn_ops = estimator_utils.estimator_spec_to_model_fn_ops(model_fn_ops)
- else:
- model_fn_ops = head.create_model_fn_ops(
- features=features,
- mode=mode,
- labels=labels,
- train_op_fn=_train_op_fn,
- logits=logits)
- if output_leaf_index and gbdt_batch.LEAF_INDEX in predictions_dict:
- model_fn_ops.predictions[gbdt_batch.LEAF_INDEX] = predictions_dict[
- gbdt_batch.LEAF_INDEX]
+ training_hooks = []
if num_trees:
if center_bias:
num_trees += 1
+
finalized_trees, attempted_trees = (
gbdt_model_main.get_number_of_trees_tensor())
- model_fn_ops.training_hooks.append(
+ training_hooks.append(
trainer_hooks.StopAfterNTrees(num_trees, attempted_trees,
finalized_trees))
+
+ if output_type == ModelBuilderOutputType.MODEL_FN_OPS:
+ if use_core_libs and callable(create_estimator_spec_op):
+ model_fn_ops = head.create_estimator_spec(
+ features=features,
+ mode=mode,
+ labels=labels,
+ train_op_fn=_train_op_fn,
+ logits=logits)
+ model_fn_ops = estimator_utils.estimator_spec_to_model_fn_ops(
+ model_fn_ops)
+ else:
+ model_fn_ops = head.create_model_fn_ops(
+ features=features,
+ mode=mode,
+ labels=labels,
+ train_op_fn=_train_op_fn,
+ logits=logits)
+
+ if output_leaf_index and gbdt_batch.LEAF_INDEX in predictions_dict:
+ model_fn_ops.predictions[gbdt_batch.LEAF_INDEX] = predictions_dict[
+ gbdt_batch.LEAF_INDEX]
+
+ model_fn_ops.training_hooks.extend(training_hooks)
+ return model_fn_ops
+
+ elif output_type == ModelBuilderOutputType.ESTIMATOR_SPEC:
+ assert callable(create_estimator_spec_op)
+ estimator_spec = head.create_estimator_spec(
+ features=features,
+ mode=mode,
+ labels=labels,
+ train_op_fn=_train_op_fn,
+ logits=logits)
+
+ estimator_spec = estimator_spec._replace(
+ training_hooks=training_hooks + list(estimator_spec.training_hooks))
+ return estimator_spec
+
return model_fn_ops
diff --git a/tensorflow/contrib/boosted_trees/kernels/quantile_ops.cc b/tensorflow/contrib/boosted_trees/kernels/quantile_ops.cc
index 0b28f81e7c..5b4be2f258 100644
--- a/tensorflow/contrib/boosted_trees/kernels/quantile_ops.cc
+++ b/tensorflow/contrib/boosted_trees/kernels/quantile_ops.cc
@@ -241,6 +241,11 @@ class CreateQuantileAccumulatorOp : public OpKernel {
// other exceptions. If one already exists, it unrefs the new one.
const Tensor* stamp_token_t;
OP_REQUIRES_OK(context, context->input(kStampTokenName, &stamp_token_t));
+ // An epsilon value of zero could cause perfoamance issues and is therefore,
+ // disallowed.
+ OP_REQUIRES(
+ context, epsilon_ > 0,
+ errors::InvalidArgument("An epsilon value of zero is not allowed."));
auto result = new QuantileStreamResource(epsilon_, num_quantiles_,
max_elements_, generate_quantiles_,
stamp_token_t->scalar<int64>()());
diff --git a/tensorflow/contrib/boosted_trees/kernels/training_ops.cc b/tensorflow/contrib/boosted_trees/kernels/training_ops.cc
index 1bfeed3066..6d9a6ee5a0 100644
--- a/tensorflow/contrib/boosted_trees/kernels/training_ops.cc
+++ b/tensorflow/contrib/boosted_trees/kernels/training_ops.cc
@@ -372,12 +372,18 @@ class GrowTreeEnsembleOp : public OpKernel {
return;
}
+ // Get the max tree depth.
+ const Tensor* max_tree_depth_t;
+ OP_REQUIRES_OK(context,
+ context->input("max_tree_depth", &max_tree_depth_t));
+ const int32 max_tree_depth = max_tree_depth_t->scalar<int32>()();
+
// Update and retrieve the growable tree.
// If the tree is fully built and dropout was applied, it also adjusts the
// weights of dropped and the last tree.
boosted_trees::trees::DecisionTreeConfig* const tree_config =
UpdateAndRetrieveGrowableTree(ensemble_resource, learning_rate,
- dropout_seed);
+ dropout_seed, max_tree_depth);
// Split tree nodes.
for (auto& split_entry : best_splits) {
@@ -494,7 +500,8 @@ class GrowTreeEnsembleOp : public OpKernel {
boosted_trees::trees::DecisionTreeConfig* UpdateAndRetrieveGrowableTree(
boosted_trees::models::DecisionTreeEnsembleResource* const
ensemble_resource,
- const float learning_rate, const uint64 dropout_seed) {
+ const float learning_rate, const uint64 dropout_seed,
+ const int32 max_tree_depth) {
const auto num_trees = ensemble_resource->num_trees();
if (num_trees <= 0 ||
ensemble_resource->LastTreeMetadata()->is_finalized()) {
@@ -506,8 +513,7 @@ class GrowTreeEnsembleOp : public OpKernel {
tree_config->add_nodes()->mutable_leaf();
boosted_trees::trees::DecisionTreeMetadata* const tree_metadata =
ensemble_resource->LastTreeMetadata();
- tree_metadata->set_is_finalized(
- learner_config_.constraints().max_tree_depth() <= 1);
+ tree_metadata->set_is_finalized(max_tree_depth <= 1);
tree_metadata->set_num_tree_weight_updates(1);
} else {
// The growable tree is by definition the last tree in the ensemble.
@@ -518,8 +524,7 @@ class GrowTreeEnsembleOp : public OpKernel {
<< num_trees - 1 << " of ensemble of " << num_trees << " trees.";
// Update growable tree metadata.
tree_metadata->set_num_layers_grown(new_num_layers);
- tree_metadata->set_is_finalized(
- new_num_layers >= learner_config_.constraints().max_tree_depth());
+ tree_metadata->set_is_finalized(new_num_layers >= max_tree_depth);
}
UpdateTreeWeightsIfDropout(ensemble_resource, dropout_seed);
return ensemble_resource->LastTree();
diff --git a/tensorflow/contrib/boosted_trees/lib/quantiles/weighted_quantiles_stream.h b/tensorflow/contrib/boosted_trees/lib/quantiles/weighted_quantiles_stream.h
index c120dd8a6c..f19e5116f5 100644
--- a/tensorflow/contrib/boosted_trees/lib/quantiles/weighted_quantiles_stream.h
+++ b/tensorflow/contrib/boosted_trees/lib/quantiles/weighted_quantiles_stream.h
@@ -58,6 +58,8 @@ namespace quantiles {
// Compute: O(n * log(1/eps * log(eps * n))).
// Memory: O(1/eps * log^2(eps * n)) <- for one worker streaming through the
// entire dataset.
+// An epsilon value of zero would make the algorithm extremely inefficent and
+// therefore, is disallowed.
template <typename ValueType, typename WeightType,
typename CompareFn = std::less<ValueType>>
class WeightedQuantilesStream {
@@ -69,6 +71,9 @@ class WeightedQuantilesStream {
explicit WeightedQuantilesStream(double eps, int64 max_elements)
: eps_(eps), buffer_(1LL, 2LL), finalized_(false) {
+ // See the class documentation. An epsilon value of zero could cause
+ // perfoamance issues.
+ QCHECK(eps > 0) << "An epsilon value of zero is not allowed.";
std::tie(max_levels_, block_size_) = GetQuantileSpecs(eps, max_elements);
buffer_ = Buffer(block_size_, max_elements);
summary_levels_.reserve(max_levels_);
diff --git a/tensorflow/contrib/boosted_trees/ops/training_ops.cc b/tensorflow/contrib/boosted_trees/ops/training_ops.cc
index f63c199ad6..22ac9edb72 100644
--- a/tensorflow/contrib/boosted_trees/ops/training_ops.cc
+++ b/tensorflow/contrib/boosted_trees/ops/training_ops.cc
@@ -56,6 +56,7 @@ REGISTER_OP("GrowTreeEnsemble")
.Input("next_stamp_token: int64")
.Input("learning_rate: float")
.Input("dropout_seed: int64")
+ .Input("max_tree_depth: int32")
.Input("partition_ids: num_handlers * int32")
.Input("gains: num_handlers * float")
.Input("splits: num_handlers * string")
@@ -67,6 +68,8 @@ REGISTER_OP("GrowTreeEnsemble")
TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &unused_input));
// Dropout seed.
TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &unused_input));
+ // Maximum tree depth.
+ TF_RETURN_IF_ERROR(c->WithRank(c->input(5), 0, &unused_input));
return Status::OK();
})
.Doc(R"doc(
diff --git a/tensorflow/contrib/boosted_trees/python/kernel_tests/training_ops_test.py b/tensorflow/contrib/boosted_trees/python/kernel_tests/training_ops_test.py
index 3e524efbea..e39e1de8d1 100644
--- a/tensorflow/contrib/boosted_trees/python/kernel_tests/training_ops_test.py
+++ b/tensorflow/contrib/boosted_trees/python/kernel_tests/training_ops_test.py
@@ -296,7 +296,7 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
pruning_mode=learner_pb2.LearnerConfig.PRE_PRUNE,
growing_mode=learner_pb2.LearnerConfig.WHOLE_TREE,
# Dropout does not change anything here, tree is not finalized.
- dropout_probability=0.5).SerializeToString()
+ dropout_probability=0.5)
# Prepare handler inputs.
# Note that handlers 1 & 3 have the same gain but different splits.
@@ -321,9 +321,10 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
],
gains=[handler1_gains, handler2_gains, handler3_gains],
splits=[handler1_split, handler2_split, handler3_split],
- learner_config=learner_config,
+ learner_config=learner_config.SerializeToString(),
dropout_seed=123,
- center_bias=True)
+ center_bias=True,
+ max_tree_depth=learner_config.constraints.max_tree_depth)
session.run(grow_op)
# Expect the simpler split from handler 1 to be chosen.
@@ -443,7 +444,7 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
pruning_mode=learner_pb2.LearnerConfig.PRE_PRUNE,
growing_mode=learner_pb2.LearnerConfig.WHOLE_TREE,
# Dropout does not change anything here - tree is not finalized.
- dropout_probability=0.5).SerializeToString()
+ dropout_probability=0.5)
# Prepare handler inputs.
# Handler 1 only has a candidate for partition 1, handler 2 has candidates
@@ -472,9 +473,10 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
],
gains=[handler1_gains, handler2_gains, handler3_gains],
splits=[handler1_split, handler2_split, handler3_split],
- learner_config=learner_config,
+ learner_config=learner_config.SerializeToString(),
dropout_seed=123,
- center_bias=True)
+ center_bias=True,
+ max_tree_depth=learner_config.constraints.max_tree_depth)
session.run(grow_op)
# Expect the split for partition 1 to be chosen from handler 1 and
@@ -632,8 +634,7 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
max_depth=1,
min_node_weight=0,
pruning_mode=learner_pb2.LearnerConfig.PRE_PRUNE,
- growing_mode=learner_pb2.LearnerConfig.WHOLE_TREE).SerializeToString(
- )
+ growing_mode=learner_pb2.LearnerConfig.WHOLE_TREE)
# Prepare handler inputs.
handler1_partitions = np.array([0], dtype=np.int32)
@@ -657,9 +658,10 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
],
gains=[handler1_gains, handler2_gains, handler3_gains],
splits=[handler1_split, handler2_split, handler3_split],
- learner_config=learner_config,
+ learner_config=learner_config.SerializeToString(),
dropout_seed=123,
- center_bias=True)
+ center_bias=True,
+ max_tree_depth=learner_config.constraints.max_tree_depth)
session.run(grow_op)
# Expect a new tree to be added with the split from handler 1.
@@ -773,8 +775,7 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
max_depth=1,
min_node_weight=0,
pruning_mode=learner_pb2.LearnerConfig.PRE_PRUNE,
- growing_mode=learner_pb2.LearnerConfig.WHOLE_TREE).SerializeToString(
- )
+ growing_mode=learner_pb2.LearnerConfig.WHOLE_TREE)
# Prepare handler inputs.
# All handlers have negative gain.
@@ -794,9 +795,10 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
partition_ids=[handler1_partitions, handler2_partitions],
gains=[handler1_gains, handler2_gains],
splits=[handler1_split, handler2_split],
- learner_config=learner_config,
+ learner_config=learner_config.SerializeToString(),
dropout_seed=123,
- center_bias=True)
+ center_bias=True,
+ max_tree_depth=learner_config.constraints.max_tree_depth)
session.run(grow_op)
# Expect the ensemble to be empty.
@@ -839,8 +841,7 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
max_depth=1,
min_node_weight=0,
pruning_mode=learner_pb2.LearnerConfig.POST_PRUNE,
- growing_mode=learner_pb2.LearnerConfig.WHOLE_TREE).SerializeToString(
- )
+ growing_mode=learner_pb2.LearnerConfig.WHOLE_TREE)
# Prepare handler inputs.
# Note that handlers 1 & 3 have the same gain but different splits.
@@ -865,9 +866,10 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
],
gains=[handler1_gains, handler2_gains, handler3_gains],
splits=[handler1_split, handler2_split, handler3_split],
- learner_config=learner_config,
+ learner_config=learner_config.SerializeToString(),
dropout_seed=123,
- center_bias=True)
+ center_bias=True,
+ max_tree_depth=learner_config.constraints.max_tree_depth)
session.run(grow_op)
# Expect the simpler split from handler 1 to be chosen.
@@ -946,8 +948,7 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
max_depth=2,
min_node_weight=0,
pruning_mode=learner_pb2.LearnerConfig.POST_PRUNE,
- growing_mode=learner_pb2.LearnerConfig.WHOLE_TREE).SerializeToString(
- )
+ growing_mode=learner_pb2.LearnerConfig.WHOLE_TREE)
# Prepare handler inputs.
# All handlers have negative gain.
@@ -967,9 +968,10 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
partition_ids=[handler1_partitions, handler2_partitions],
gains=[handler1_gains, handler2_gains],
splits=[handler1_split, handler2_split],
- learner_config=learner_config,
+ learner_config=learner_config.SerializeToString(),
dropout_seed=123,
- center_bias=True)
+ center_bias=True,
+ max_tree_depth=learner_config.constraints.max_tree_depth)
session.run(grow_op)
# Expect the split from handler 2 to be chosen despite the negative gain.
@@ -1048,9 +1050,10 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
partition_ids=[handler1_partitions],
gains=[handler1_gains],
splits=[handler1_split],
- learner_config=learner_config,
+ learner_config=learner_config.SerializeToString(),
dropout_seed=123,
- center_bias=True)
+ center_bias=True,
+ max_tree_depth=learner_config.constraints.max_tree_depth)
session.run(grow_op)
# Expect the ensemble to be empty as post-pruning will prune
@@ -1094,8 +1097,7 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
max_depth=2,
min_node_weight=0,
pruning_mode=learner_pb2.LearnerConfig.POST_PRUNE,
- growing_mode=learner_pb2.LearnerConfig.WHOLE_TREE).SerializeToString(
- )
+ growing_mode=learner_pb2.LearnerConfig.WHOLE_TREE)
# Prepare handler inputs.
# Second handler has positive gain.
@@ -1115,9 +1117,10 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
partition_ids=[handler1_partitions, handler2_partitions],
gains=[handler1_gains, handler2_gains],
splits=[handler1_split, handler2_split],
- learner_config=learner_config,
+ learner_config=learner_config.SerializeToString(),
dropout_seed=123,
- center_bias=True)
+ center_bias=True,
+ max_tree_depth=learner_config.constraints.max_tree_depth)
session.run(grow_op)
# Expect the split from handler 2 to be chosen despite the negative gain.
@@ -1194,9 +1197,10 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
partition_ids=[handler1_partitions],
gains=[handler1_gains],
splits=[handler1_split],
- learner_config=learner_config,
+ learner_config=learner_config.SerializeToString(),
dropout_seed=123,
- center_bias=True)
+ center_bias=True,
+ max_tree_depth=learner_config.constraints.max_tree_depth)
session.run(grow_op)
# Expect the negative gain split of partition 1 to be pruned and the
@@ -1335,7 +1339,7 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
pruning_mode=learner_pb2.LearnerConfig.PRE_PRUNE,
growing_mode=learner_pb2.LearnerConfig.LAYER_BY_LAYER,
# Dropout will have no effect, since the tree will not be fully grown.
- dropout_probability=1.0).SerializeToString()
+ dropout_probability=1.0)
# Prepare handler inputs.
# Handler 1 only has a candidate for partition 1, handler 2 has candidates
@@ -1364,9 +1368,10 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
],
gains=[handler1_gains, handler2_gains, handler3_gains],
splits=[handler1_split, handler2_split, handler3_split],
- learner_config=learner_config,
+ learner_config=learner_config.SerializeToString(),
dropout_seed=123,
- center_bias=True)
+ center_bias=True,
+ max_tree_depth=learner_config.constraints.max_tree_depth)
session.run(grow_op)
# Expect the split for partition 1 to be chosen from handler 1 and
@@ -1543,7 +1548,7 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
min_node_weight=0,
pruning_mode=learner_pb2.LearnerConfig.PRE_PRUNE,
growing_mode=learner_pb2.LearnerConfig.WHOLE_TREE,
- dropout_probability=1.0).SerializeToString()
+ dropout_probability=1.0)
# Prepare handler inputs.
handler1_partitions = np.array([0], dtype=np.int32)
@@ -1567,9 +1572,10 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
],
gains=[handler1_gains, handler2_gains, handler3_gains],
splits=[handler1_split, handler2_split, handler3_split],
- learner_config=learner_config,
+ learner_config=learner_config.SerializeToString(),
dropout_seed=123,
- center_bias=True)
+ center_bias=True,
+ max_tree_depth=learner_config.constraints.max_tree_depth)
session.run(grow_op)
# Expect a new tree to be added with the split from handler 1.
@@ -1669,7 +1675,6 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
growing_mode=learner_pb2.LearnerConfig.WHOLE_TREE)
learner_config.constraints.max_number_of_unique_feature_columns = 3
- learner_config = learner_config.SerializeToString()
# Prepare handler inputs.
handler1_partitions = np.array([0], dtype=np.int32)
handler1_gains = np.array([7.62], dtype=np.float32)
@@ -1692,9 +1697,10 @@ class GrowTreeEnsembleOpTest(test_util.TensorFlowTestCase):
],
gains=[handler1_gains, handler2_gains, handler3_gains],
splits=[handler1_split, handler2_split, handler3_split],
- learner_config=learner_config,
+ learner_config=learner_config.SerializeToString(),
dropout_seed=123,
- center_bias=True)
+ center_bias=True,
+ max_tree_depth=learner_config.constraints.max_tree_depth)
session.run(grow_op)
_, serialized = session.run(
diff --git a/tensorflow/contrib/boosted_trees/python/training/functions/gbdt_batch.py b/tensorflow/contrib/boosted_trees/python/training/functions/gbdt_batch.py
index 643d8d2498..19e053fcb6 100644
--- a/tensorflow/contrib/boosted_trees/python/training/functions/gbdt_batch.py
+++ b/tensorflow/contrib/boosted_trees/python/training/functions/gbdt_batch.py
@@ -380,6 +380,8 @@ class GradientBoostedDecisionTreeModel(object):
self._learner_config = learner_config
self._feature_columns = feature_columns
self._learner_config_serialized = learner_config.SerializeToString()
+ self._max_tree_depth = variables.Variable(
+ initial_value=self._learner_config.constraints.max_tree_depth)
self._attempted_trees = variables.Variable(
initial_value=array_ops.zeros([], dtypes.int64),
trainable=False,
@@ -520,9 +522,6 @@ class GradientBoostedDecisionTreeModel(object):
if not input_deps:
raise ValueError("No input tensors for prediction.")
- if any(i.device != input_deps[0].device for i in input_deps):
- raise ValueError("All input tensors should be on the same device.")
-
# Get most current model stamp.
ensemble_stamp = model_ops.tree_ensemble_stamp_token(self._ensemble_handle)
@@ -1054,7 +1053,8 @@ class GradientBoostedDecisionTreeModel(object):
splits=split_info_list,
learner_config=self._learner_config_serialized,
dropout_seed=dropout_seed,
- center_bias=self._center_bias)
+ center_bias=self._center_bias,
+ max_tree_depth=self._max_tree_depth)
def _grow_ensemble_not_ready_fn():
# Don't grow the ensemble, just update the stamp.
@@ -1068,7 +1068,8 @@ class GradientBoostedDecisionTreeModel(object):
splits=[],
learner_config=self._learner_config_serialized,
dropout_seed=dropout_seed,
- center_bias=self._center_bias)
+ center_bias=self._center_bias,
+ max_tree_depth=self._max_tree_depth)
def _grow_ensemble_fn():
# Conditionally grow an ensemble depending on whether the splits
@@ -1108,6 +1109,9 @@ class GradientBoostedDecisionTreeModel(object):
def get_number_of_trees_tensor(self):
return self._finalized_trees, self._attempted_trees
+ def get_max_tree_depth(self):
+ return self._max_tree_depth
+
def train(self, loss, predictions_dict, labels):
"""Updates the accumalator stats and grows the ensemble.
diff --git a/tensorflow/contrib/cloud/kernels/bigquery_table_accessor.cc b/tensorflow/contrib/cloud/kernels/bigquery_table_accessor.cc
index 1bfd27305d..58fadffce3 100644
--- a/tensorflow/contrib/cloud/kernels/bigquery_table_accessor.cc
+++ b/tensorflow/contrib/cloud/kernels/bigquery_table_accessor.cc
@@ -85,7 +85,7 @@ Status BigQueryTableAccessor::New(
int64 timestamp_millis, int64 row_buffer_size, const string& end_point,
const std::vector<string>& columns, const BigQueryTablePartition& partition,
std::unique_ptr<AuthProvider> auth_provider,
- std::unique_ptr<HttpRequest::Factory> http_request_factory,
+ std::shared_ptr<HttpRequest::Factory> http_request_factory,
std::unique_ptr<BigQueryTableAccessor>* accessor) {
if (timestamp_millis <= 0) {
return errors::InvalidArgument(
@@ -94,29 +94,19 @@ Status BigQueryTableAccessor::New(
const string& big_query_end_point =
end_point.empty() ? kBigQueryEndPoint : end_point;
if (auth_provider == nullptr && http_request_factory == nullptr) {
- accessor->reset(new BigQueryTableAccessor(
- project_id, dataset_id, table_id, timestamp_millis, row_buffer_size,
- big_query_end_point, columns, partition));
- } else {
- accessor->reset(new BigQueryTableAccessor(
- project_id, dataset_id, table_id, timestamp_millis, row_buffer_size,
- big_query_end_point, columns, partition, std::move(auth_provider),
- std::move(http_request_factory)));
+ http_request_factory = std::make_shared<CurlHttpRequest::Factory>();
+ auto compute_engine_metadata_client =
+ std::make_shared<ComputeEngineMetadataClient>(http_request_factory);
+ auth_provider = std::unique_ptr<AuthProvider>(
+ new GoogleAuthProvider(compute_engine_metadata_client));
}
- return (*accessor)->ReadSchema();
-}
-BigQueryTableAccessor::BigQueryTableAccessor(
- const string& project_id, const string& dataset_id, const string& table_id,
- int64 timestamp_millis, int64 row_buffer_size, const string& end_point,
- const std::vector<string>& columns, const BigQueryTablePartition& partition)
- : BigQueryTableAccessor(
- project_id, dataset_id, table_id, timestamp_millis, row_buffer_size,
- end_point, columns, partition,
- std::unique_ptr<AuthProvider>(new GoogleAuthProvider()),
- std::unique_ptr<HttpRequest::Factory>(
- new CurlHttpRequest::Factory())) {
- row_buffer_.resize(row_buffer_size);
+ accessor->reset(new BigQueryTableAccessor(
+ project_id, dataset_id, table_id, timestamp_millis, row_buffer_size,
+ big_query_end_point, columns, partition, std::move(auth_provider),
+ std::move(http_request_factory)));
+
+ return (*accessor)->ReadSchema();
}
BigQueryTableAccessor::BigQueryTableAccessor(
@@ -124,7 +114,7 @@ BigQueryTableAccessor::BigQueryTableAccessor(
int64 timestamp_millis, int64 row_buffer_size, const string& end_point,
const std::vector<string>& columns, const BigQueryTablePartition& partition,
std::unique_ptr<AuthProvider> auth_provider,
- std::unique_ptr<HttpRequest::Factory> http_request_factory)
+ std::shared_ptr<HttpRequest::Factory> http_request_factory)
: project_id_(project_id),
dataset_id_(dataset_id),
table_id_(table_id),
diff --git a/tensorflow/contrib/cloud/kernels/bigquery_table_accessor.h b/tensorflow/contrib/cloud/kernels/bigquery_table_accessor.h
index b349063715..1af43a3e10 100644
--- a/tensorflow/contrib/cloud/kernels/bigquery_table_accessor.h
+++ b/tensorflow/contrib/cloud/kernels/bigquery_table_accessor.h
@@ -109,24 +109,17 @@ class BigQueryTableAccessor {
const std::vector<string>& columns,
const BigQueryTablePartition& partition,
std::unique_ptr<AuthProvider> auth_provider,
- std::unique_ptr<HttpRequest::Factory> http_request_factory,
+ std::shared_ptr<HttpRequest::Factory> http_request_factory,
std::unique_ptr<BigQueryTableAccessor>* accessor);
/// \brief Constructs an object for a given table and partition.
- BigQueryTableAccessor(const string& project_id, const string& dataset_id,
- const string& table_id, int64 timestamp_millis,
- int64 row_buffer_size, const string& end_point,
- const std::vector<string>& columns,
- const BigQueryTablePartition& partition);
-
- /// Used for unit testing.
BigQueryTableAccessor(
const string& project_id, const string& dataset_id,
const string& table_id, int64 timestamp_millis, int64 row_buffer_size,
const string& end_point, const std::vector<string>& columns,
const BigQueryTablePartition& partition,
std::unique_ptr<AuthProvider> auth_provider,
- std::unique_ptr<HttpRequest::Factory> http_request_factory);
+ std::shared_ptr<HttpRequest::Factory> http_request_factory);
/// \brief Parses column values for a given row.
Status ParseColumnValues(const Json::Value& value,
@@ -199,7 +192,7 @@ class BigQueryTableAccessor {
SchemaNode schema_root_;
std::unique_ptr<AuthProvider> auth_provider_;
- std::unique_ptr<HttpRequest::Factory> http_request_factory_;
+ std::shared_ptr<HttpRequest::Factory> http_request_factory_;
TF_DISALLOW_COPY_AND_ASSIGN(BigQueryTableAccessor);
};
diff --git a/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver.py b/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver.py
index 8f521ffee4..f9dc3effd0 100644
--- a/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver.py
+++ b/tensorflow/contrib/cluster_resolver/python/training/tpu_cluster_resolver.py
@@ -259,11 +259,11 @@ class TPUClusterResolver(ClusterResolver):
if 'state' in response and response['state'] != 'READY':
raise RuntimeError('TPU "%s" is not yet ready; state: "%s"' %
- (self._tpu, response['state']))
+ (compat.as_text(self._tpu), response['state']))
if 'health' in response and response['health'] != 'HEALTHY':
- raise RuntimeError('TPU "%s" is unhealthy: "%s"' % (self._tpu,
- response['health']))
+ raise RuntimeError('TPU "%s" is unhealthy: "%s"' %
+ (compat.as_text(self._tpu), response['health']))
if 'networkEndpoints' in response:
worker_list = [
diff --git a/tensorflow/contrib/cmake/CMakeLists.txt b/tensorflow/contrib/cmake/CMakeLists.txt
index 6c93487e0d..f6c928e2be 100644
--- a/tensorflow/contrib/cmake/CMakeLists.txt
+++ b/tensorflow/contrib/cmake/CMakeLists.txt
@@ -471,7 +471,6 @@ if (tensorflow_ENABLE_GPU)
${CUDA_TOOLKIT_TARGET_DIR}/include/cuComplex.h
${CUDA_TOOLKIT_TARGET_DIR}/include/cublas_v2.h
${CUDA_TOOLKIT_TARGET_DIR}/include/cusolverDn.h
- ${CUDA_TOOLKIT_TARGET_DIR}/include/cuda_fp16.h
${CUDA_TOOLKIT_TARGET_DIR}/include/device_functions.h
${CUDA_TOOLKIT_TARGET_DIR}/include/cufft.h
${CUDA_TOOLKIT_TARGET_DIR}/include/curand.h
diff --git a/tensorflow/contrib/copy_graph/python/util/copy_elements.py b/tensorflow/contrib/copy_graph/python/util/copy_elements.py
index 5931c8a279..6c9ab6aeb8 100644
--- a/tensorflow/contrib/copy_graph/python/util/copy_elements.py
+++ b/tensorflow/contrib/copy_graph/python/util/copy_elements.py
@@ -219,8 +219,10 @@ def copy_op_to_graph(org_instance, to_graph, variables, scope=''):
op_def)
#Use Graph's hidden methods to add the op
to_graph._record_op_seen_by_control_dependencies(new_op)
- for device_function in reversed(to_graph._device_function_stack):
+ # pylint: disable=protected-access
+ for device_function in to_graph._device_functions_outer_to_inner:
new_op._set_device(device_function(new_op))
+ # pylint: enable=protected-access
return new_op
diff --git a/tensorflow/contrib/data/kernels/prefetching_kernels.cc b/tensorflow/contrib/data/kernels/prefetching_kernels.cc
index 6edc61b2c2..32f03ca683 100644
--- a/tensorflow/contrib/data/kernels/prefetching_kernels.cc
+++ b/tensorflow/contrib/data/kernels/prefetching_kernels.cc
@@ -791,16 +791,17 @@ class MultiDeviceIteratorGetNextFromShardOp : public AsyncOpKernel {
void ComputeAsync(OpKernelContext* ctx, DoneCallback done) override {
const Tensor* tensor_shard_num;
- OP_REQUIRES_OK(ctx, ctx->input("shard_num", &tensor_shard_num));
+ OP_REQUIRES_OK_ASYNC(ctx, ctx->input("shard_num", &tensor_shard_num), done);
int32 shard_num = tensor_shard_num->scalar<int32>()();
const Tensor* tensor_incarnation_id;
- OP_REQUIRES_OK(ctx, ctx->input("incarnation_id", &tensor_incarnation_id));
+ OP_REQUIRES_OK_ASYNC(
+ ctx, ctx->input("incarnation_id", &tensor_incarnation_id), done);
int64 incarnation_id = tensor_incarnation_id->scalar<int64>()();
MultiDeviceIterator* iterator;
- OP_REQUIRES_OK(ctx,
- LookupResource(ctx, HandleFromInput(ctx, 0), &iterator));
+ OP_REQUIRES_OK_ASYNC(
+ ctx, LookupResource(ctx, HandleFromInput(ctx, 0), &iterator), done);
thread_pool_->Schedule(std::bind(
[ctx, iterator, shard_num, incarnation_id](DoneCallback done) {
std::vector<Tensor> components;
diff --git a/tensorflow/contrib/data/python/kernel_tests/BUILD b/tensorflow/contrib/data/python/kernel_tests/BUILD
index 036dc795bb..2de1a79d28 100644
--- a/tensorflow/contrib/data/python/kernel_tests/BUILD
+++ b/tensorflow/contrib/data/python/kernel_tests/BUILD
@@ -192,6 +192,7 @@ py_test(
deps = [
"//tensorflow/contrib/data/python/ops:batching",
"//tensorflow/contrib/data/python/ops:error_ops",
+ "//tensorflow/contrib/data/python/ops:optimization",
"//tensorflow/python:array_ops",
"//tensorflow/python:client_testlib",
"//tensorflow/python:errors",
@@ -213,6 +214,7 @@ py_test(
"//tensorflow/python:client_testlib",
"//tensorflow/python:errors",
"//tensorflow/python/data/ops:dataset_ops",
+ "@absl_py//absl/testing:parameterized",
],
)
@@ -234,7 +236,12 @@ cuda_py_test(
"//tensorflow/python/data/ops:dataset_ops",
"//tensorflow/python/data/ops:iterator_ops",
],
- tags = ["no_windows_gpu"],
+ tags = [
+ "manual",
+ "no_oss",
+ "no_windows_gpu" +
+ "notap",
+ ],
)
py_test(
diff --git a/tensorflow/contrib/data/python/kernel_tests/map_dataset_op_test.py b/tensorflow/contrib/data/python/kernel_tests/map_dataset_op_test.py
index b7025f3802..48adc98e9a 100644
--- a/tensorflow/contrib/data/python/kernel_tests/map_dataset_op_test.py
+++ b/tensorflow/contrib/data/python/kernel_tests/map_dataset_op_test.py
@@ -26,6 +26,7 @@ import numpy as np
from tensorflow.contrib.data.python.ops import batching
from tensorflow.contrib.data.python.ops import error_ops
+from tensorflow.contrib.data.python.ops import optimization
from tensorflow.core.protobuf import config_pb2
from tensorflow.python.client import session
from tensorflow.python.data.ops import dataset_ops
@@ -82,15 +83,17 @@ class MapDatasetTest(test.TestCase):
def write_string_to_file(value, filename):
with open(filename, "w") as f:
f.write(value)
- filenames = [os.path.join(self.get_temp_dir(), "file_%d.txt" % i)
- for i in range(5)]
+
+ filenames = [
+ os.path.join(self.get_temp_dir(), "file_%d.txt" % i) for i in range(5)
+ ]
for filename in filenames:
write_string_to_file(filename, filename)
dataset = (
dataset_ops.Dataset.from_tensor_slices(filenames).map(
- io_ops.read_file, num_parallel_calls=2).prefetch(2).apply(
- error_ops.ignore_errors()))
+ io_ops.read_file,
+ num_parallel_calls=2).prefetch(2).apply(error_ops.ignore_errors()))
iterator = dataset.make_initializable_iterator()
init_op = iterator.initializer
get_next = iterator.get_next()
@@ -264,5 +267,46 @@ class MapDatasetBenchmark(test.Benchmark):
benchmark("Transformation parallelism evaluation", par_num_calls_series)
benchmark("Threadpool size evaluation", par_inter_op_series)
+ # This benchmark compares the performance of pipeline with multiple chained
+ # maps with and without map fusion.
+ def benchmarkChainOfMaps(self):
+ chain_lengths = [0, 1, 2, 5, 10, 20, 50]
+ for chain_length in chain_lengths:
+ self._benchmarkChainOfMaps(chain_length, False)
+ self._benchmarkChainOfMaps(chain_length, True)
+
+ def _benchmarkChainOfMaps(self, chain_length, optimize_dataset):
+ with ops.Graph().as_default():
+ dataset = dataset_ops.Dataset.from_tensors(0).repeat(None)
+ for _ in range(chain_length):
+ dataset = dataset.map(lambda x: x)
+ if optimize_dataset:
+ dataset = dataset.apply(optimization.optimize(["map_fusion"]))
+
+ iterator = dataset.make_one_shot_iterator()
+ next_element = iterator.get_next()
+
+ with session.Session() as sess:
+ for _ in range(5):
+ sess.run(next_element.op)
+ deltas = []
+ for _ in range(100):
+ start = time.time()
+ for _ in range(100):
+ sess.run(next_element.op)
+ end = time.time()
+ deltas.append(end - start)
+
+ median_wall_time = np.median(deltas) / 100
+ opt_mark = "opt" if optimize_dataset else "no-opt"
+ print("Map dataset {} chain length: {} Median wall time: {}".format(
+ opt_mark, chain_length, median_wall_time))
+ self.report_benchmark(
+ iters=1000,
+ wall_time=median_wall_time,
+ name="benchmark_map_dataset_chain_latency_{}_{}".format(
+ opt_mark, chain_length))
+
+
if __name__ == "__main__":
test.main()
diff --git a/tensorflow/contrib/data/python/kernel_tests/optimize_dataset_op_test.py b/tensorflow/contrib/data/python/kernel_tests/optimize_dataset_op_test.py
index cfef40e192..d8156dc9c7 100644
--- a/tensorflow/contrib/data/python/kernel_tests/optimize_dataset_op_test.py
+++ b/tensorflow/contrib/data/python/kernel_tests/optimize_dataset_op_test.py
@@ -17,13 +17,15 @@ from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
+from absl.testing import parameterized
+
from tensorflow.contrib.data.python.ops import optimization
from tensorflow.python.data.ops import dataset_ops
from tensorflow.python.framework import errors
from tensorflow.python.platform import test
-class OptimizeDatasetTest(test.TestCase):
+class OptimizeDatasetTest(test.TestCase, parameterized.TestCase):
def testAssertSuffix(self):
dataset = dataset_ops.Dataset.from_tensors(0).apply(
@@ -110,6 +112,53 @@ class OptimizeDatasetTest(test.TestCase):
"Function .* is not defined."):
sess.run(get_next)
+ @staticmethod
+ def map_functions():
+ identity = lambda x: x
+ increment = lambda x: x + 1
+
+ def increment_and_square(x):
+ y = x + 1
+ return y * y
+
+ functions = [identity, increment, increment_and_square]
+ tests = []
+
+ for fun1 in functions:
+ for fun2 in functions:
+ tests.append(([fun1, fun2],))
+ for fun3 in functions:
+ tests.append(([fun1, fun2, fun3],))
+
+ swap = lambda x, n: (n, x)
+ tests.append(([lambda x: (x, 42), swap],))
+ tests.append(([lambda x: (x, 42), swap, swap],))
+ return tuple(tests)
+
+ @parameterized.parameters(*map_functions.__func__())
+ def testMapFusion(self, functions):
+ dataset = dataset_ops.Dataset.range(5).apply(
+ optimization.assert_next(["Map", "Prefetch"]))
+ for function in functions:
+ dataset = dataset.map(function)
+
+ dataset = dataset.prefetch(0).apply(optimization.optimize(["map_fusion"]))
+ iterator = dataset.make_one_shot_iterator()
+ get_next = iterator.get_next()
+ with self.test_session() as sess:
+ for x in range(5):
+ result = sess.run(get_next)
+ r = x
+ for function in functions:
+ if isinstance(r, tuple):
+ r = function(*r) # Pass tuple as multiple arguments.
+ else:
+ r = function(r)
+ self.assertAllEqual(r, result)
+
+ with self.assertRaises(errors.OutOfRangeError):
+ sess.run(get_next)
+
if __name__ == "__main__":
test.main()
diff --git a/tensorflow/contrib/data/python/ops/batching.py b/tensorflow/contrib/data/python/ops/batching.py
index a4914f4cde..42fc20ec01 100644
--- a/tensorflow/contrib/data/python/ops/batching.py
+++ b/tensorflow/contrib/data/python/ops/batching.py
@@ -515,10 +515,7 @@ def batch_and_drop_remainder(batch_size):
def _apply_fn(dataset):
"""Function from `Dataset` to `Dataset` that applies the transformation."""
- # TODO(jsimsa): Switch to using `batch(..., drop_remainder=True)` any time
- # after 6/30/2018.
- batched = dataset.batch(batch_size)
- return _filter_irregular_batches(batch_size)(batched)
+ return dataset.batch(batch_size, drop_remainder=True)
return _apply_fn
@@ -553,11 +550,9 @@ def padded_batch_and_drop_remainder(batch_size,
def _apply_fn(dataset):
"""Function from `Dataset` to `Dataset` that applies the transformation."""
- # TODO(jsimsa): Switch to using `padded_batch(..., drop_remainder=True)`
- # any time after 6/30/2018.
- batched = dataset.padded_batch(
- batch_size, padded_shapes=padded_shapes, padding_values=padding_values)
- return _filter_irregular_batches(batch_size)(batched)
+ return dataset.padded_batch(
+ batch_size, padded_shapes=padded_shapes, padding_values=padding_values,
+ drop_remainder=True)
return _apply_fn
diff --git a/tensorflow/contrib/distribute/python/BUILD b/tensorflow/contrib/distribute/python/BUILD
index f5d7e24ae2..cbe741de5a 100644
--- a/tensorflow/contrib/distribute/python/BUILD
+++ b/tensorflow/contrib/distribute/python/BUILD
@@ -101,6 +101,23 @@ py_library(
)
py_library(
+ name = "parameter_server_strategy",
+ srcs = ["parameter_server_strategy.py"],
+ visibility = ["//tensorflow:internal"],
+ deps = [
+ ":cross_tower_ops",
+ ":mirrored_strategy",
+ ":values",
+ "//tensorflow/core:protos_all_py",
+ "//tensorflow/python:array_ops",
+ "//tensorflow/python:framework_ops",
+ "//tensorflow/python:resource_variable_ops",
+ "//tensorflow/python:training",
+ "//tensorflow/python:util",
+ ],
+)
+
+py_library(
name = "one_device_strategy",
srcs = ["one_device_strategy.py"],
visibility = ["//tensorflow:internal"],
@@ -207,6 +224,35 @@ py_test(
],
)
+py_test(
+ name = "parameter_server_strategy_test",
+ srcs = ["parameter_server_strategy_test.py"],
+ srcs_version = "PY2AND3",
+ tags = [
+ "no_pip",
+ ],
+ deps = [
+ ":combinations",
+ ":multi_worker_test_base",
+ ":parameter_server_strategy",
+ "//tensorflow/core:protos_all_py",
+ "//tensorflow/python:array_ops",
+ "//tensorflow/python:client_testlib",
+ "//tensorflow/python:constant_op",
+ "//tensorflow/python:control_flow_ops",
+ "//tensorflow/python:framework_ops",
+ "//tensorflow/python:gradients",
+ "//tensorflow/python:layers",
+ "//tensorflow/python:session",
+ "//tensorflow/python:training",
+ "//tensorflow/python:variable_scope",
+ "//tensorflow/python:variables",
+ "//tensorflow/python/eager:context",
+ "//tensorflow/python/estimator:run_config",
+ "@absl_py//absl/testing:parameterized",
+ ],
+)
+
cuda_py_test(
name = "mirrored_strategy_multigpu_test",
srcs = ["mirrored_strategy_multigpu_test.py"],
diff --git a/tensorflow/contrib/distribute/python/checkpoint_utils_test.py b/tensorflow/contrib/distribute/python/checkpoint_utils_test.py
index fe3df9cbb9..bcb977f640 100644
--- a/tensorflow/contrib/distribute/python/checkpoint_utils_test.py
+++ b/tensorflow/contrib/distribute/python/checkpoint_utils_test.py
@@ -49,17 +49,23 @@ class CheckpointUtilsWithDistributionStrategyTest(
def testInitFromCheckpoint(self, distribution, in_tower_mode):
checkpoint_dir = self.get_temp_dir()
with self.test_session() as session:
- v1_value, _, _, _ = checkpoint_utils_test._create_checkpoints(
+ v1_value, v2_value, _, _ = checkpoint_utils_test._create_checkpoints(
session, checkpoint_dir)
def init_and_verify(g):
v1 = variable_scope.get_variable("new_var1", [1, 10])
+ v2 = variable_scope.get_variable(
+ "new_var2", [10, 10],
+ synchronization=variable_scope.VariableSynchronization.ON_READ,
+ aggregation=variable_scope.VariableAggregation.MEAN)
checkpoint_utils.init_from_checkpoint(checkpoint_dir, {
"var1": "new_var1",
+ "var2": "new_var2"
})
with self.test_session(graph=g) as session:
session.run(variables.global_variables_initializer())
self.assertAllEqual(v1_value, self.evaluate(v1))
+ self.assertAllEqual(v2_value, self.evaluate(v2))
with ops.Graph().as_default() as g, distribution.scope():
if in_tower_mode:
diff --git a/tensorflow/contrib/distribute/python/combinations.py b/tensorflow/contrib/distribute/python/combinations.py
index 9a8ea4aa48..52f73ddb03 100644
--- a/tensorflow/contrib/distribute/python/combinations.py
+++ b/tensorflow/contrib/distribute/python/combinations.py
@@ -144,7 +144,7 @@ def _augment_with_special_arguments(test_method):
"""A wrapped test method that treats some arguments in a special way."""
mode = kwargs.pop("mode", "graph")
- distribution = kwargs.pop("distribution", None)
+ distribution = kwargs.get("distribution", None)
required_tpu = kwargs.pop("required_tpu", False)
required_gpus = kwargs.pop("required_gpus", None)
@@ -153,7 +153,6 @@ def _augment_with_special_arguments(test_method):
"Do not use `required_gpus` and `distribution` together.")
assert required_tpu is False, (
"Do not use `required_tpu` and `distribution` together.")
- kwargs["distribution"] = distribution.strategy
required_gpus = distribution.required_gpus
required_tpu = distribution.required_tpu
@@ -189,9 +188,13 @@ def _augment_with_special_arguments(test_method):
if mode == "eager":
with ops.Graph().as_default(), context.eager_mode():
+ if distribution:
+ kwargs_to_pass["distribution"] = distribution.strategy
test_method(**kwargs_to_pass)
elif mode == "graph":
with ops.Graph().as_default(), context.graph_mode():
+ if distribution:
+ kwargs_to_pass["distribution"] = distribution.strategy
test_method(**kwargs_to_pass)
else:
raise ValueError(
diff --git a/tensorflow/contrib/distribute/python/cross_tower_ops.py b/tensorflow/contrib/distribute/python/cross_tower_ops.py
index b0baf0dad1..b6037d2133 100644
--- a/tensorflow/contrib/distribute/python/cross_tower_ops.py
+++ b/tensorflow/contrib/distribute/python/cross_tower_ops.py
@@ -28,18 +28,37 @@ from tensorflow.python.eager import context
from tensorflow.python.framework import ops
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import math_ops
+from tensorflow.python.ops import resource_variable_ops
from tensorflow.python.ops import variable_scope as vs
from tensorflow.python.platform import tf_logging as logging
from tensorflow.python.training import device_util
+def check_destinations(destinations):
+ """Checks whether `destinations` is not None and not empty.
+
+ Args:
+ destinations: a DistributedValues, Variable, string or a list of strings.
+
+ Returns:
+ Boolean indicating whether `destinations` is not None and not empty.
+ """
+ # Calling bool() on a ResourceVariable is not allowed.
+ if isinstance(destinations, resource_variable_ops.ResourceVariable):
+ return bool(destinations.device)
+ return bool(destinations)
+
+
def validate_destinations(destinations):
- if not isinstance(destinations,
- (value_lib.DistributedValues, six.string_types, list)):
+ if not isinstance(
+ destinations,
+ (value_lib.DistributedValues, resource_variable_ops.ResourceVariable,
+ six.string_types, list)):
raise ValueError("destinations must be one of a `DistributedValues` object,"
- " a device string, a list of device strings or None")
+ " a tf.Variable object, a device string, a list of device "
+ "strings or None")
- if not destinations:
+ if not check_destinations(destinations):
raise ValueError("destinations can not be empty")
@@ -59,6 +78,8 @@ def _validate_value_destination_pairs(value_destination_pairs):
def get_devices_from(destinations):
if isinstance(destinations, value_lib.DistributedValues):
return list(destinations.devices)
+ elif isinstance(destinations, resource_variable_ops.ResourceVariable):
+ return [destinations.device]
elif isinstance(destinations, six.string_types):
return [device_util.resolve(destinations)]
else:
@@ -225,7 +246,10 @@ class ReductionToOneDeviceCrossTowerOps(CrossTowerOps):
super(ReductionToOneDeviceCrossTowerOps, self).__init__()
def _reduce(self, aggregation, per_device_value, destinations):
- devices = get_devices_from(destinations or per_device_value)
+ if check_destinations(destinations):
+ devices = get_devices_from(destinations)
+ else:
+ devices = get_devices_from(per_device_value)
reduce_to_device = self.reduce_to_device or devices[0]
reduced = _simple_reduce(per_device_value, reduce_to_device,
self.accumulation_fn, aggregation)
@@ -508,7 +532,10 @@ class AllReduceCrossTowerOps(CrossTowerOps):
logging.WARN,
"Efficient allreduce is not supported for IndexedSlices.", 10)
- devices = get_devices_from(destinations or per_device_value)
+ if check_destinations(destinations):
+ devices = get_devices_from(destinations)
+ else:
+ devices = get_devices_from(per_device_value)
reduce_to_device = devices[0]
reduced = _simple_reduce(per_device_value, reduce_to_device,
math_ops.add_n, aggregation)
diff --git a/tensorflow/contrib/distribute/python/estimator_integration_test.py b/tensorflow/contrib/distribute/python/estimator_integration_test.py
index 34410a6470..a0bb144b7c 100644
--- a/tensorflow/contrib/distribute/python/estimator_integration_test.py
+++ b/tensorflow/contrib/distribute/python/estimator_integration_test.py
@@ -96,7 +96,8 @@ class DNNLinearCombinedClassifierIntegrationTest(test.TestCase,
# TODO(isaprykin): Work around the colocate_with error.
dnn_optimizer=adagrad.AdagradOptimizer(0.001),
linear_optimizer=adagrad.AdagradOptimizer(0.001),
- config=run_config.RunConfig(train_distribute=distribution))
+ config=run_config.RunConfig(
+ train_distribute=distribution, eval_distribute=distribution))
num_steps = 10
estimator.train(train_input_fn, steps=num_steps)
diff --git a/tensorflow/contrib/distribute/python/examples/simple_estimator_example.py b/tensorflow/contrib/distribute/python/examples/simple_estimator_example.py
index 00c25c7a24..44a69ed23a 100644
--- a/tensorflow/contrib/distribute/python/examples/simple_estimator_example.py
+++ b/tensorflow/contrib/distribute/python/examples/simple_estimator_example.py
@@ -59,7 +59,8 @@ def build_model_fn_optimizer():
def main(_):
distribution = tf.contrib.distribute.MirroredStrategy(
["/device:GPU:0", "/device:GPU:1"])
- config = tf.estimator.RunConfig(train_distribute=distribution)
+ config = tf.estimator.RunConfig(train_distribute=distribution,
+ eval_distribute=distribution)
def input_fn():
features = tf.data.Dataset.from_tensors([[1.]]).repeat(10)
@@ -70,7 +71,7 @@ def main(_):
model_fn=build_model_fn_optimizer(), config=config)
estimator.train(input_fn=input_fn, steps=10)
- eval_result = estimator.evaluate(input_fn=input_fn)
+ eval_result = estimator.evaluate(input_fn=input_fn, steps=10)
print("Eval result: {}".format(eval_result))
def predict_input_fn():
diff --git a/tensorflow/contrib/distribute/python/examples/simple_tfkeras_example.py b/tensorflow/contrib/distribute/python/examples/simple_tfkeras_example.py
index 2b05884b9b..518ec9c423 100644
--- a/tensorflow/contrib/distribute/python/examples/simple_tfkeras_example.py
+++ b/tensorflow/contrib/distribute/python/examples/simple_tfkeras_example.py
@@ -57,7 +57,8 @@ def main(args):
# tf.Estimator that utilizes the DistributionStrategy.
strategy = tf.contrib.distribute.MirroredStrategy(
['/device:GPU:0', '/device:GPU:1'])
- config = tf.estimator.RunConfig(train_distribute=strategy)
+ config = tf.estimator.RunConfig(
+ train_distribute=strategy, eval_distribute=strategy)
keras_estimator = tf.keras.estimator.model_to_estimator(
keras_model=model, config=config, model_dir=model_dir)
diff --git a/tensorflow/contrib/distribute/python/keras_test.py b/tensorflow/contrib/distribute/python/keras_test.py
index 75ecd90dcf..34b8a54d7b 100644
--- a/tensorflow/contrib/distribute/python/keras_test.py
+++ b/tensorflow/contrib/distribute/python/keras_test.py
@@ -107,7 +107,8 @@ class TestKerasDistributionStrategy(test_util.TensorFlowTestCase):
optimizer=rmsprop.RMSPropOptimizer(learning_rate=0.01))
config = run_config_lib.RunConfig(tf_random_seed=_RANDOM_SEED,
model_dir=self._base_dir,
- train_distribute=dist)
+ train_distribute=dist,
+ eval_distribute=dist)
with self.test_session():
est_keras = keras_lib.model_to_estimator(
keras_model=keras_model, config=config)
diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy.py b/tensorflow/contrib/distribute/python/mirrored_strategy.py
index dcbc6b0878..eb2d102012 100644
--- a/tensorflow/contrib/distribute/python/mirrored_strategy.py
+++ b/tensorflow/contrib/distribute/python/mirrored_strategy.py
@@ -20,7 +20,6 @@ from __future__ import print_function
import contextlib
import threading
-import six
from tensorflow.contrib.distribute.python import cross_tower_ops as cross_tower_ops_lib
from tensorflow.contrib.distribute.python import shared_variable_creator
@@ -60,6 +59,156 @@ class _RequestedStop(Exception):
pass
+# Make _call_for_each_tower and _reduce_non_distributed_value not members of
+# MirroredStrategy so that they are generally not allowed to use anything
+# specific to MirroredStrategy and thus can be shared with other distribution
+# strategies.
+
+
+# TODO(yuefengz): maybe create a common class for those who need to call this
+# _call_for_each_tower.
+def _call_for_each_tower(distribution, fn, *args, **kwargs):
+ """Run `fn` in separate threads, once per tower/worker device.
+
+ Args:
+ distribution: the DistributionStrategy object.
+ fn: function to run (will be run once per device, each in its own thread).
+ *args: positional arguments for `fn`
+ **kwargs: keyword arguments for `fn`.
+ `"run_concurrently"`: Boolean indicating whether executions of `fn`
+ can be run concurrently (under eager execution only), defaults to
+ `True`.
+
+ Returns:
+ Merged return value of `fn` across all towers.
+
+ Raises:
+ RuntimeError: If fn() calls get_tower_context().merge_call() a different
+ number of times from the available devices.
+ """
+ run_concurrently = kwargs.pop("run_concurrently", True)
+ if not context.executing_eagerly():
+ # Lots of TF library code isn't thread-safe in graph mode, and
+ # there is little to be gained by turning on multithreading when
+ # constructing a graph.
+ run_concurrently = False
+ # Needed for per-thread device, etc. contexts in graph mode.
+ ops.get_default_graph().switch_to_thread_local()
+ elif run_concurrently is None:
+ run_concurrently = True
+
+ coord = coordinator.Coordinator(clean_stop_exception_types=(_RequestedStop,))
+
+ shared_variable_store = {}
+
+ # TODO(isaprykin): Create these threads once instead of during every run()
+ # call.
+ threads = []
+ for index, d in enumerate(distribution.worker_devices):
+ variable_creator_fn = shared_variable_creator.make_fn(
+ shared_variable_store, index)
+ t = MirroredStrategy._MirroredTowerThread( # pylint: disable=protected-access
+ distribution, coord, d, variable_creator_fn, fn,
+ *values.select_device(d, args), **values.select_device(d, kwargs))
+ threads.append(t)
+
+ for t in threads:
+ t.start()
+
+ # When `fn` starts `should_run` event is set on _MirroredTowerThread
+ # (`MTT`) threads. The execution waits until
+ # `MTT.has_paused` is set, which indicates that either `fn` is
+ # complete or a `get_tower_context().merge_call()` is called. If `fn` is
+ # complete, then `MTT.done` is set to True. Otherwise, arguments
+ # of `get_tower_context().merge_call` from all paused threads are grouped
+ # and the `merge_fn` is performed. Results of the
+ # `get_tower_context().merge_call` are then set to `MTT.merge_result`.
+ # Each such `get_tower_context().merge_call` call returns the
+ # `MTT.merge_result` for that thread when `MTT.should_run` event
+ # is reset again. Execution of `fn` resumes.
+
+ try:
+ with coord.stop_on_exception():
+ all_done = False
+ while not all_done and not coord.should_stop():
+ done = []
+ if run_concurrently:
+ for t in threads:
+ t.should_run.set()
+ for t in threads:
+ t.has_paused.wait()
+ t.has_paused.clear()
+ if coord.should_stop():
+ return None
+ done.append(t.done)
+ else:
+ for t in threads:
+ t.should_run.set()
+ t.has_paused.wait()
+ t.has_paused.clear()
+ if coord.should_stop():
+ return None
+ done.append(t.done)
+ if coord.should_stop():
+ return None
+ all_done = all(done)
+ if not all_done:
+ if any(done):
+ raise RuntimeError("Some towers made a different number of "
+ "tower_context().merge_call() calls.")
+ # get_tower_context().merge_call() case
+ merge_args = values.regroup({t.device: t.merge_args for t in threads})
+ merge_kwargs = values.regroup(
+ {t.device: t.merge_kwargs for t in threads})
+ # We capture the name_scope of the MTT when we call merge_fn
+ # to ensure that if we have opened a name scope in the MTT,
+ # it will be respected when executing the merge function. We only
+ # capture the name_scope from the first MTT and assume it is
+ # the same for all other MTTs.
+ mtt_captured_name_scope = threads[0].captured_name_scope
+ with ops.name_scope(mtt_captured_name_scope):
+ merge_result = threads[0].merge_fn(distribution, *merge_args,
+ **merge_kwargs)
+ for t in threads:
+ t.merge_result = values.select_device(t.device, merge_result)
+ finally:
+ for t in threads:
+ t.should_run.set()
+ coord.join(threads)
+
+ return values.regroup({t.device: t.main_result for t in threads})
+
+
+def _reduce_non_distributed_value(distribution, aggregation, value,
+ destinations):
+ """Reduce a non-DistributedValue `value` to `destinations`."""
+ if isinstance(value, values.DistributedValues):
+ raise ValueError("You are passing a `DistributedValue` to "
+ "`_reduce_non_distributed_value`, which is not allowed.")
+
+ if value == 0:
+ return 0
+ if aggregation == variable_scope.VariableAggregation.MEAN:
+ return distribution.broadcast(value, destinations)
+
+ cross_tower_ops_lib.validate_destinations(destinations)
+ if (len(distribution.worker_devices) != 1 or
+ not cross_tower_ops_lib.check_destinations(destinations)):
+ raise ValueError("A non-DistributedValues value cannot be reduced with the "
+ "given aggregation.")
+ # TODO(anjalisridhar): Moves these methods to a device utility file?
+ devices = cross_tower_ops_lib.get_devices_from(destinations)
+ if len(devices) == 1:
+ with ops.device(devices[0]):
+ return array_ops.identity(value)
+ else:
+ value_updates = {}
+ for d in devices:
+ with ops.device(d):
+ value_updates[d] = array_ops.identity(value)
+ return values.Mirrored(value_updates)
+
+
class MirroredStrategy(distribute_lib.DistributionStrategy):
"""Mirrors vars to distribute across multiple devices on a single machine.
@@ -198,116 +347,7 @@ class MirroredStrategy(distribute_lib.DistributionStrategy):
self._devices)
def _call_for_each_tower(self, fn, *args, **kwargs):
- """Run `fn` in separate threads, once per tower/worker device.
-
- Args:
- fn: function to run (will be run once per device, each in its own thread).
- *args: positional arguments for `fn`
- **kwargs: keyword arguments for `fn`.
- `"run_concurrently"`: Boolean indicating whether executions of `fn`
- can be run concurrently (under eager execution only), defaults to
- `True`.
-
- Returns:
- Merged return value of `fn` across all towers.
-
- Raises:
- RuntimeError: If fn() calls get_tower_context().merge_call() a different
- number of times for when called for different devices.
- """
- run_concurrently = kwargs.pop("run_concurrently", True)
- if not context.executing_eagerly():
- # Lots of TF library code isn't thread-safe in graph mode, and
- # there is little to be gained by turning on multithreading when
- # constructing a graph.
- run_concurrently = False
- # Needed for per-thread device, etc. contexts in graph mode.
- ops.get_default_graph().switch_to_thread_local()
- elif run_concurrently is None:
- run_concurrently = True
-
- coord = coordinator.Coordinator(
- clean_stop_exception_types=(_RequestedStop,))
-
- shared_variable_store = {}
-
- # TODO(isaprykin): Create these threads once instead of during every run()
- # call.
- threads = []
- for index, d in enumerate(self._devices):
- variable_creator_fn = shared_variable_creator.make_fn(
- shared_variable_store, index)
- t = MirroredStrategy._MirroredTowerThread(
- self, coord, d, variable_creator_fn, fn,
- *values.select_device(d, args), **values.select_device(d, kwargs))
- threads.append(t)
-
- for t in threads:
- t.start()
-
- # When `fn` starts `should_run` event is set on _MirroredTowerThread
- # (`MTT`) threads. The execution waits until
- # `MTT.has_paused` is set, which indicates that either `fn` is
- # complete or a `get_tower_context().merge_call()` is called. If `fn` is
- # complete, then `MTT.done` is set to True. Otherwise, arguments
- # of `get_tower_context().merge_call` from all paused threads are grouped
- # and the `merge_fn` is performed. Results of the
- # `get_tower_context().merge_call` are then set to `MTT.merge_result`.
- # Each such `get_tower_context().merge_call` call returns the
- # `MTT.merge_result` for that thread when `MTT.should_run` event
- # is reset again. Execution of `fn` resumes.
-
- try:
- with coord.stop_on_exception():
- all_done = False
- while not all_done and not coord.should_stop():
- done = []
- if run_concurrently:
- for t in threads:
- t.should_run.set()
- for t in threads:
- t.has_paused.wait()
- t.has_paused.clear()
- if coord.should_stop():
- return None
- done.append(t.done)
- else:
- for t in threads:
- t.should_run.set()
- t.has_paused.wait()
- t.has_paused.clear()
- if coord.should_stop():
- return None
- done.append(t.done)
- if coord.should_stop():
- return None
- all_done = all(done)
- if not all_done:
- if any(done):
- raise RuntimeError("Some towers made a different number of "
- "tower_context().merge_call() calls.")
- # get_tower_context().merge_call() case
- merge_args = values.regroup(
- {t.device: t.merge_args for t in threads})
- merge_kwargs = values.regroup(
- {t.device: t.merge_kwargs for t in threads})
- # We capture the name_scope of the MTT when we call merge_fn
- # to ensure that if we have opened a name scope in the MTT,
- # it will be respected when executing the merge function. We only
- # capture the name_scope from the first MTT and assume it is
- # the same for all other MTTs.
- mtt_captured_name_scope = threads[0].captured_name_scope
- with ops.name_scope(mtt_captured_name_scope):
- merge_result = threads[0].merge_fn(
- self, *merge_args, **merge_kwargs)
- for t in threads:
- t.merge_result = values.select_device(t.device, merge_result)
- finally:
- for t in threads:
- t.should_run.set()
- coord.join(threads)
-
- return values.regroup({t.device: t.main_result for t in threads})
+ return _call_for_each_tower(self, fn, *args, **kwargs)
def map(self, map_over, fn, *args, **kwargs):
# TODO(josh11b): In eager mode, use one thread per device.
@@ -337,29 +377,9 @@ class MirroredStrategy(distribute_lib.DistributionStrategy):
def _reduce(self, aggregation, value, destinations):
assert not isinstance(value, values.Mirrored)
- if not isinstance(value, values.PerDevice):
- if value == 0:
- return 0
- if aggregation == variable_scope.VariableAggregation.MEAN:
- return self._broadcast(value, destinations)
-
- cross_tower_ops_lib.validate_destinations(destinations)
- if len(self._devices) == 1:
- if destinations:
- # TODO(anjalisridhar): Moves these methods to a device utility file?
- devices = cross_tower_ops_lib.get_devices_from(destinations)
- if len(devices) == 1:
- with ops.device(devices[0]):
- return array_ops.identity(value)
- else:
- value_updates = {}
- for d in devices:
- with ops.device(d):
- value_updates[d] = array_ops.identity(value)
- return values.Mirrored(value_updates)
- raise ValueError("A non PerDevice value cannot be reduced with the given "
- "aggregation.")
-
+ if not isinstance(value, values.DistributedValues):
+ return _reduce_non_distributed_value(self, aggregation, value,
+ destinations)
return self._get_cross_tower_ops().reduce(
aggregation, value, destinations=destinations)
@@ -433,15 +453,8 @@ class MirroredStrategy(distribute_lib.DistributionStrategy):
def _get_devices_from(self, colocate_with=None):
if colocate_with is None:
return self._devices
- elif isinstance(colocate_with, values.DistributedValues):
- # pylint: disable=protected-access
- return list(colocate_with._index.keys())
- elif isinstance(colocate_with, six.string_types):
- return [device_util.resolve(colocate_with)]
- elif isinstance(colocate_with, list):
- return [device_util.resolve(d) for d in colocate_with]
else:
- return colocate_with
+ return cross_tower_ops_lib.get_devices_from(colocate_with)
class _MirroredTowerThread(threading.Thread):
"""A thread that runs() a function on a device."""
diff --git a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py
index 9807ce4351..aab7119901 100644
--- a/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py
+++ b/tensorflow/contrib/distribute/python/mirrored_strategy_multigpu_test.py
@@ -792,8 +792,8 @@ class MirroredVariableUpdateTest(test.TestCase):
return mirrored_var.assign(5.0)
with self.assertRaisesRegexp(
- ValueError, "A non PerDevice value cannot be reduced with the given "
- "aggregation."):
+ ValueError, "A non-DistributedValues value cannot be reduced with "
+ "the given aggregation."):
self.evaluate(dist.unwrap(dist.call_for_each_tower(model_fn)))
@test_util.run_in_graph_and_eager_modes(config=config)
diff --git a/tensorflow/contrib/distribute/python/multi_worker_test_base.py b/tensorflow/contrib/distribute/python/multi_worker_test_base.py
index f659be5f42..fa479918bd 100644
--- a/tensorflow/contrib/distribute/python/multi_worker_test_base.py
+++ b/tensorflow/contrib/distribute/python/multi_worker_test_base.py
@@ -28,23 +28,39 @@ from tensorflow.python.eager import test
from tensorflow.python.framework import test_util
+def create_in_process_cluster(num_workers, num_ps):
+ """Create an in-process cluster that consists of only standard server."""
+ # Leave some memory for cuda runtime.
+ gpu_mem_frac = 0.7 / num_workers
+ worker_config = config_pb2.ConfigProto()
+ worker_config.gpu_options.per_process_gpu_memory_fraction = gpu_mem_frac
+
+ ps_config = config_pb2.ConfigProto()
+ ps_config.device_count['GPU'] = 0
+
+ # Create in-process servers. Once an in-process tensorflow server is created,
+ # there is no way to terminate it. So we create one cluster per test process.
+ # We could've started the server in another process, we could then kill that
+ # process to terminate the server. The reasons why we don't want multiple
+ # processes are
+ # 1) it is more difficult to manage these processes
+ # 2) there is something global in CUDA such that if we initialize CUDA in the
+ # parent process, the child process cannot initialize it again and thus cannot
+ # use GPUs (https://stackoverflow.com/questions/22950047).
+ return test_util.create_local_cluster(
+ num_workers,
+ num_ps=num_ps,
+ worker_config=worker_config,
+ ps_config=ps_config)
+
+
class MultiWorkerTestBase(test.TestCase):
"""Base class for testing multi node strategy and dataset."""
@classmethod
def setUpClass(cls):
"""Create a local cluster with 2 workers."""
- num_workers = 2
- # Leave some memory for cuda runtime.
- gpu_mem_frac = 0.7 / num_workers
- default_config = config_pb2.ConfigProto()
- default_config.gpu_options.per_process_gpu_memory_fraction = gpu_mem_frac
-
- # The local cluster takes some portion of the local GPUs and there is no way
- # for the cluster to terminate unless using multiple processes. Therefore,
- # we have to only create only one cluster throughout a test process.
- workers, _ = test_util.create_local_cluster(
- num_workers, num_ps=0, worker_config=default_config)
+ workers, _ = create_in_process_cluster(num_workers=2, num_ps=0)
cls._master_target = workers[0].target
@contextlib.contextmanager
diff --git a/tensorflow/contrib/distribute/python/parameter_server_strategy.py b/tensorflow/contrib/distribute/python/parameter_server_strategy.py
new file mode 100644
index 0000000000..9bcf6f8bac
--- /dev/null
+++ b/tensorflow/contrib/distribute/python/parameter_server_strategy.py
@@ -0,0 +1,355 @@
+# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Classes implementing a multi-worker ps DistributionStrategy."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import json
+import os
+
+from tensorflow.contrib.distribute.python import cross_tower_ops as cross_tower_ops_lib
+from tensorflow.contrib.distribute.python import mirrored_strategy
+from tensorflow.contrib.distribute.python import values
+from tensorflow.core.protobuf import cluster_pb2
+from tensorflow.python.framework import device as tf_device
+from tensorflow.python.framework import ops
+from tensorflow.python.ops import array_ops
+from tensorflow.python.ops import resource_variable_ops
+from tensorflow.python.training import device_setter
+from tensorflow.python.training import device_util
+from tensorflow.python.training import distribute as distribute_lib
+from tensorflow.python.training import server_lib
+from tensorflow.python.util import nest
+
+_LOCAL_CPU = "/device:CPU:0"
+_LOCAL_GPU_0 = "/device:GPU:0"
+
+
+def _normalize_cluster_spec(cluster_spec):
+ """Makes `cluster_spec` into a `ClusterSpec` object."""
+ if isinstance(cluster_spec, (dict, cluster_pb2.ClusterDef)):
+ return server_lib.ClusterSpec(cluster_spec)
+ elif not isinstance(cluster_spec, server_lib.ClusterSpec):
+ raise ValueError(
+ "`cluster_spec' should be dict or a `tf.train.ClusterSpec` or a "
+ "`tf.train.ClusterDef` object")
+ return cluster_spec
+
+
+# TODO(yuefengz): maybe cache variables on local CPU.
+# TODO(yuefengz): we may want to set session options to disallow communication
+# between workers.
+class ParameterServerStrategy(distribute_lib.DistributionStrategy):
+ """A parameter server DistributionStrategy.
+
+ This strategy class works for both local training and between-graph replicated
+ training for multiple workers. If `cluster_spec` is specified, either passed
+ in to __init__() method or parsed from the
+ ["TF_CONFIG" environment
+ variable](https://www.tensorflow.org/api_docs/python/tf/estimator/RunConfig),
+ variables and updates to those variables are assigned to parameter servers and
+ other operations are assigned to workers. If `cluster_spec` is not set, it
+ becomes local training where variables are assigned to local CPU or the only
+ GPU. When each worker has more than one GPU, operations will be replicated on
+ these GPUs. In both cases, operations are replicated but variables are not and
+ these workers share a common view for which paramater server a variable is
+ assigned to.
+
+ This class assumes between-graph replication will be used and works on a graph
+ for a particular worker.
+
+ It is expected to call `call_for_each_tower(fn, *args, **kwargs)` for any
+ operations which potentially can be replicated across towers (i.e. multiple
+ GPUs) even if there is only CPU or one GPU. When defining the `fn`, extra
+ caution needs to be taken:
+
+ 1) Always use @{tf.get_variable} instead of @{tf.Variable} which is not able
+ to refer to the same variable on different towers.
+
+ 2) It is generally not recommended to open a device scope under the strategy's
+ scope. A device scope (i.e. calling @{tf.device}) will be merged with or
+ override the device for operations but will not change the device for
+ variables.
+
+ 3) It is also not recommended to open a colocation scope (i.e. calling
+ @{tf.colocate_with}) under the strategy's scope. For colocating variables,
+ use `distribution.colocate_vars_with` instead. Colocation of ops will possibly
+ create conflicts of device assignement.
+ """
+
+ def __init__(self,
+ num_gpus_per_worker=0,
+ cluster_spec=None,
+ task_type=None,
+ task_id=None):
+ """Initiailizes this strategy.
+
+ Args:
+ num_gpus_per_worker: number of local GPUs or GPUs per worker.
+ cluster_spec: a dict, ClusterDef or ClusterSpec object specifying the
+ cluster configurations.
+ task_type: the current task type.
+ task_id: the current task id.
+ """
+ super(ParameterServerStrategy, self).__init__()
+ self._num_gpus_per_worker = num_gpus_per_worker
+ if cluster_spec:
+ cluster_spec = _normalize_cluster_spec(cluster_spec)
+ self._cluster_spec = cluster_spec
+
+ # We typically don't need to do all-reduce in this strategy.
+ self._cross_tower_ops = (
+ cross_tower_ops_lib.ReductionToOneDeviceCrossTowerOps(
+ reduce_to_device=_LOCAL_CPU))
+
+ self._initialize_devices(num_gpus_per_worker, cluster_spec, task_type,
+ task_id)
+
+ def _initialize_devices(self, num_gpus_per_worker, cluster_spec, task_type,
+ task_id):
+ """Initialize internal devices.
+
+ It creates variable devices and compute devices. Variables and operations
+ will be assigned to them respectively. We have one compute device per tower.
+ The variable device is a device function or device string. The default
+ variable device assigns variables to parameter servers in a round-robin
+ fashion.
+
+ Args:
+ num_gpus_per_worker: number of local GPUs or GPUs per worker.
+ cluster_spec: a dict, ClusterDef or ClusterSpec object specifying the
+ cluster configurations.
+ task_type: the current task type.
+ task_id: the current task id.
+
+ Raises:
+ ValueError: if the cluster_spec doesn't have ps jobs.
+ """
+ self._task_type = task_type or "worker"
+ self._task_id = task_id or 0
+ self._worker_device = "/job:%s/task:%d" % (self._task_type, self._task_id)
+
+ # TODO(yuefengz): maybe clearer to split it into two classes, one for
+ # the distribuetd case and one for the local case, once we have the factory
+ # class/method.
+
+ # Define compute devices which is a list of device strings and one for each
+ # tower. When there are GPUs, replicate operations on these GPUs. Otherwise,
+ # place operations on CPU.
+ if cluster_spec is None:
+ # Local mode.
+ if num_gpus_per_worker > 0:
+ self._compute_devices = list(
+ map("/device:GPU:{}".format, range(num_gpus_per_worker)))
+ else:
+ self._compute_devices = [_LOCAL_CPU]
+ else:
+ # Distributed mode.
+ if num_gpus_per_worker > 0:
+ self._compute_devices = [
+ "%s/device:GPU:%d" % (self._worker_device, i)
+ for i in range(num_gpus_per_worker)
+ ]
+ else:
+ self._compute_devices = [self._worker_device]
+
+ self._compute_devices = list(
+ map(device_util.resolve, self._compute_devices))
+ self._canonical_compute_device_set = set(self._compute_devices)
+
+ # Define variable device which is a device string in the local case and a
+ # device function in the distributed case. It is used to open a device scope
+ # where varibles are defined.
+ # The `_parameter_devices` is needed for the `parameter_devices` property
+ # and is a list of all variable devices.
+ if cluster_spec is None:
+ # Local mode. If there is only one GPU, put everything on that GPU.
+ # Otherwise, place variables on CPU.
+ if num_gpus_per_worker == 1:
+ assert len(list(self._compute_devices)) == 1
+ self._variable_device = _LOCAL_GPU_0
+ self._parameter_devices = [_LOCAL_GPU_0]
+ else:
+ self._variable_device = _LOCAL_CPU
+ self._parameter_devices = [_LOCAL_CPU]
+ else:
+ # Distributed mode. Place variables on ps jobs in a round-robin fashion.
+ # Note that devices returned from `replica_device_setter` are not
+ # canonical and therefore we don't canonicalize all variable devices to
+ # make them consistent.
+ # TODO(yuefengz): support passing a strategy object to control variable
+ # assignment.
+ # TODO(yuefengz): merge the logic of replica_device_setter into this
+ # class.
+ num_ps_replicas = len(cluster_spec.as_dict().get("ps", []))
+ if num_ps_replicas == 0:
+ raise ValueError("The cluster spec needs to have `ps` jobs.")
+ self._variable_device = device_setter.replica_device_setter(
+ ps_tasks=num_ps_replicas,
+ worker_device=self._worker_device,
+ merge_devices=True,
+ cluster=cluster_spec)
+
+ # Parameter devices are all tasks of the "ps" job.
+ self._parameter_devices = map("/job:ps/task:{}".format,
+ range(num_ps_replicas))
+
+ # Define the default device in cross-tower mode. In the distributed case, we
+ # set the default device to the corresponding worker to prevent these ops
+ # from being placed on other workers.
+ if cluster_spec is None:
+ self._default_device = None
+ else:
+ self._default_device = self._worker_device
+
+ def distribute_dataset(self, dataset_fn):
+ """Distributes the dataset to each local GPU."""
+ return values.PerDeviceDataset(
+ self._call_dataset_fn(dataset_fn), self._compute_devices, True)
+
+ def _broadcast(self, tensor, destinations):
+ if not cross_tower_ops_lib.check_destinations(destinations):
+ destinations = self._compute_devices
+ return self._cross_tower_ops.broadcast(tensor, destinations)
+
+ # TODO(yuefengz): not all ops in device_setter.STANDARD_PS_OPS will go through
+ # this creator, such as "MutableHashTable".
+ def _create_variable(self, next_creator, *args, **kwargs):
+ if "colocate_with" in kwargs:
+ with ops.device(None):
+ with ops.colocate_with(kwargs["colocate_with"]):
+ return next_creator(*args, **kwargs)
+
+ with ops.colocate_with(None, ignore_existing=True):
+ with ops.device(self._variable_device):
+ return next_creator(*args, **kwargs)
+
+ def _call_for_each_tower(self, fn, *args, **kwargs):
+ # pylint: disable=protected-access
+ return mirrored_strategy._call_for_each_tower(self, fn, *args, **kwargs)
+
+ def _verify_destinations_not_different_worker(self, destinations):
+ if destinations is None:
+ return
+ for d in cross_tower_ops_lib.get_devices_from(destinations):
+ d_spec = tf_device.DeviceSpec.from_string(d)
+ if d_spec.job == self._task_type and d_spec.task != self._task_id:
+ raise ValueError(
+ "Cannot reduce to another worker: %r, current worker is %r" %
+ (d, self._worker_device))
+
+ def _reduce(self, aggregation, value, destinations):
+ self._verify_destinations_not_different_worker(destinations)
+ if not isinstance(value, values.DistributedValues):
+ # pylint: disable=protected-access
+ return mirrored_strategy._reduce_non_distributed_value(
+ self, aggregation, value, destinations)
+
+ return self._cross_tower_ops.reduce(
+ aggregation, value, destinations=destinations)
+
+ def _batch_reduce(self, aggregation, value_destination_pairs):
+ for _, destinations in value_destination_pairs:
+ self._verify_destinations_not_different_worker(destinations)
+ return self._cross_tower_ops.batch_reduce(aggregation,
+ value_destination_pairs)
+
+ def _select_single_value(self, structured):
+ """Select any single values in `structured`."""
+
+ def _select_fn(x): # pylint: disable=g-missing-docstring
+ if isinstance(x, values.Mirrored):
+ if len(x.devices) == 1:
+ return list(x._index.values())[0] # pylint: disable=protected-access
+ else:
+ raise ValueError(
+ "You cannot update variable with a Mirrored object with multiple "
+ "components %r when using ParameterServerStrategy. You must "
+ "specify a single value or a Mirrored with a single value." % x)
+ elif isinstance(x, values.PerDevice):
+ raise ValueError(
+ "You cannot update variable with a PerDevice object %r when using "
+ "ParameterServerStrategy. You must specify a single value or a "
+ "Mirrored with a single value" % x)
+ else:
+ return x
+
+ return nest.map_structure(_select_fn, structured)
+
+ def _update(self, var, fn, *args, **kwargs):
+ if not isinstance(var, resource_variable_ops.ResourceVariable):
+ raise ValueError(
+ "You can not update `var` %r. It must be a Variable." % var)
+ with ops.colocate_with(var), distribute_lib.UpdateContext(var.device):
+ return fn(var, *self._select_single_value(args),
+ **self._select_single_value(kwargs))
+
+ # TODO(yuefengz): does it need to call _select_single_value?
+ def _update_non_slot(self, colocate_with, fn, *args, **kwargs):
+ with ops.device(
+ colocate_with.device), distribute_lib.UpdateContext(colocate_with):
+ return fn(*args, **kwargs)
+
+ def _unwrap(self, val):
+ if isinstance(val, values.DistributedValues):
+ # Return in a deterministic order.
+ if set(val.devices) == self._canonical_compute_device_set:
+ return [val.get(device=d) for d in self._compute_devices]
+ return [val.get(device=d) for d in sorted(val.devices)]
+ return [val]
+
+ def read_var(self, var):
+ # No need to distinguish between normal variables and tower-local variables.
+ return array_ops.identity(var)
+
+ def configure(self, session_config=None):
+ del session_config
+
+ # Use TF_CONFIG to get the cluster spec and the current job.
+ tf_config = json.loads(os.environ.get("TF_CONFIG", "{}"))
+ cluster_spec = _normalize_cluster_spec(tf_config.get("cluster", {}))
+
+ task_env = tf_config.get("task", {})
+ if task_env:
+ task_type = task_env.get("type", "worker")
+ task_id = int(task_env.get("index", "0"))
+ else:
+ task_type = "worker"
+ task_id = None
+
+ # Set the devices if cluster_spec is defined in TF_CONFIG but not passed in
+ # the constructor.
+ if not self._cluster_spec and cluster_spec:
+ self._cluster_spec = cluster_spec
+ self._initialize_devices(self._num_gpus_per_worker, cluster_spec,
+ task_type, task_id)
+
+ @property
+ def num_towers(self):
+ return len(self._compute_devices)
+
+ @property
+ def worker_devices(self):
+ # Make a copy to prevent users from accidentally mutating our copy.
+ return list(self._compute_devices)
+
+ @property
+ def parameter_devices(self):
+ return list(self._parameter_devices)
+
+ def non_slot_devices(self, var_list):
+ return min(var_list, key=lambda x: x.name)
diff --git a/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py b/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py
new file mode 100644
index 0000000000..ad538b9e8e
--- /dev/null
+++ b/tensorflow/contrib/distribute/python/parameter_server_strategy_test.py
@@ -0,0 +1,455 @@
+# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Tests for ParameterServerStrategy."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import contextlib
+import json
+import threading
+from absl.testing import parameterized
+
+from tensorflow.contrib.distribute.python import combinations
+from tensorflow.contrib.distribute.python import multi_worker_test_base
+from tensorflow.contrib.distribute.python import parameter_server_strategy
+from tensorflow.core.protobuf import config_pb2
+from tensorflow.python.client import session
+from tensorflow.python.eager import context
+from tensorflow.python.estimator import run_config
+from tensorflow.python.framework import constant_op
+from tensorflow.python.framework import ops
+from tensorflow.python.layers import core
+from tensorflow.python.ops import array_ops
+from tensorflow.python.ops import control_flow_ops
+from tensorflow.python.ops import gradients
+from tensorflow.python.ops import variable_scope
+from tensorflow.python.ops import variables
+from tensorflow.python.platform import test
+from tensorflow.python.training import device_util
+from tensorflow.python.training import distribute as distribute_lib
+
+
+class ParameterServerStrategyTest(test.TestCase, parameterized.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls._workers, cls._ps = multi_worker_test_base.create_in_process_cluster(
+ num_workers=3, num_ps=2)
+
+ def setUp(self):
+ self._result = 0
+ self._lock = threading.Lock()
+ self._init_condition = threading.Condition()
+ self._init_reached = 0
+ self._finish_condition = threading.Condition()
+ self._finish_reached = 0
+
+ def _get_ps_distribution_strategy(self, task_type, task_index, num_gpus=0):
+ tf_config = {
+ 'cluster': {
+ run_config.TaskType.WORKER: [
+ 'fake_worker_0', 'fake_worker_1', 'fake_worker_2'
+ ],
+ run_config.TaskType.PS: ['fake_ps_0', 'fake_ps_1']
+ },
+ 'task': {
+ 'type': task_type,
+ 'index': task_index
+ }
+ }
+ distribution = parameter_server_strategy.ParameterServerStrategy(
+ num_gpus_per_worker=num_gpus)
+ with self._lock:
+ # Accessing environment variables should be protected by locks because
+ # environment variables are shared by all threads.
+ with test.mock.patch.dict('os.environ',
+ {'TF_CONFIG': json.dumps(tf_config)}):
+ distribution.configure()
+ return distribution
+
+ @contextlib.contextmanager
+ def _test_session(self, target):
+ config = config_pb2.ConfigProto(allow_soft_placement=True)
+ config.graph_options.optimizer_options.opt_level = -1
+ with session.Session(graph=None, config=config, target=target) as sess:
+ yield sess
+
+ def _test_device_assignment_distributed(self, d, num_gpus=0):
+ with ops.Graph().as_default(), \
+ self._test_session(target=self._workers[0].target) as sess, \
+ d.scope():
+
+ # Define a variable outside the call_for_each_tower scope. This is not
+ # recommended.
+ n = variable_scope.get_variable('n', initializer=10.0)
+ self.assertEqual(n.device, '/job:ps/task:0')
+
+ def model_fn():
+ if num_gpus == 0:
+ last_part_device = 'device:CPU:0'
+ else:
+ last_part_device = (
+ 'device:GPU:%d' % distribute_lib.get_tower_context().tower_id)
+
+ a = constant_op.constant(1.0)
+ b = constant_op.constant(2.0)
+ c = a + b
+ self.assertEqual(a.device,
+ '/job:worker/replica:0/task:1/%s' % last_part_device)
+ self.assertEqual(b.device,
+ '/job:worker/replica:0/task:1/%s' % last_part_device)
+ self.assertEqual(c.device,
+ '/job:worker/replica:0/task:1/%s' % last_part_device)
+
+ # The device scope is ignored for variables but not for normal ops.
+ with ops.device('/job:worker/task:0'):
+ x = variable_scope.get_variable('x', initializer=10.0)
+ x_add = x.assign_add(c)
+ e = a + c
+ # The variable x is on the task 1 since the device_function has been
+ # called once before the model_fn.
+ self.assertEqual(x.device, '/job:ps/task:1')
+ self.assertEqual(x_add.device, x.device)
+ self.assertEqual(e.device,
+ '/job:worker/replica:0/task:0/%s' % last_part_device)
+
+ # The colocate_vars_with can override the distribution's device.
+ with d.colocate_vars_with(x):
+ y = variable_scope.get_variable('y', initializer=20.0)
+ y_add = y.assign_add(x_add)
+ self.assertEqual(y.device, '/job:ps/task:1')
+ self.assertEqual(y_add.device, y.device)
+ self.assertEqual(y.device, x.device)
+
+ z = variable_scope.get_variable('z', initializer=10.0)
+ self.assertEqual(z.device, '/job:ps/task:0')
+ self.assertNotEqual(z.device, x.device)
+
+ with ops.control_dependencies([y_add]):
+ z_add = z.assign_add(y)
+ with ops.control_dependencies([z_add]):
+ f = z + c
+ self.assertEqual(f.device,
+ '/job:worker/replica:0/task:1/%s' % last_part_device)
+
+ # The device scope would merge with the default worker device.
+ with ops.device('/CPU:1'):
+ g = e + 1.0
+ self.assertEqual(g.device, '/job:worker/replica:0/task:1/device:CPU:1')
+
+ # Ths ops.colocate_with will be ignored when defining a variale but not
+ # for a normal tensor.
+ with ops.colocate_with(x):
+ u = variable_scope.get_variable('u', initializer=30.0)
+ v = variable_scope.get_variable('v', initializer=30.0)
+ h = f + 1.0
+ self.assertIn('/job:ps/', u.device)
+ self.assertIn('/job:ps/', v.device)
+ # u and v are on different parameter servers.
+ self.assertTrue(u.device != x.device or v.device != x.device)
+ self.assertTrue(u.device == x.device or v.device == x.device)
+ # Here h is not on one worker. Note h.device is canonical while x.device
+ # is not but.
+ self.assertIn('/job:ps/', h.device)
+ return y_add, z_add, f
+
+ y, z, f = d.call_for_each_tower(model_fn)
+ self.assertNotEqual(y, None)
+ self.assertNotEqual(z, None)
+ self.assertNotEqual(f, None)
+
+ if context.num_gpus() >= 1 and num_gpus <= 1:
+ variables.global_variables_initializer().run()
+ y_val, z_val, f_val = sess.run([y, z, f])
+ self.assertEqual(y_val, 33.0)
+ self.assertEqual(z_val, 43.0)
+ self.assertEqual(f_val, 46.0)
+
+ @combinations.generate(
+ combinations.combine(mode=['graph'], num_gpus=[0, 1, 2]))
+ def testDeviceAssignmentDistributed(self, num_gpus):
+ d = self._get_ps_distribution_strategy('worker', 1, num_gpus=num_gpus)
+ self._test_device_assignment_distributed(d, num_gpus=num_gpus)
+
+ def _test_device_assignment_local(self,
+ d,
+ compute_device='CPU',
+ variable_device='CPU',
+ num_gpus=0):
+ with ops.Graph().as_default(), \
+ self._test_session(target=self._workers[0].target) as sess, \
+ d.scope():
+
+ def model_fn():
+ if 'CPU' in compute_device:
+ tower_compute_device = '/device:CPU:0'
+ else:
+ tower_compute_device = (
+ '/device:GPU:%d' % distribute_lib.get_tower_context().tower_id)
+ tower_compute_device = device_util.canonicalize(tower_compute_device)
+
+ if 'CPU' in variable_device:
+ tower_variable_device = '/device:CPU:0'
+ else:
+ tower_variable_device = (
+ '/device:GPU:%d' % distribute_lib.get_tower_context().tower_id)
+ tower_variable_device = device_util.canonicalize(tower_variable_device)
+
+ a = constant_op.constant(1.0)
+ b = constant_op.constant(2.0)
+ c = a + b
+ self.assertEqual(a.device, tower_compute_device)
+ self.assertEqual(b.device, tower_compute_device)
+ self.assertEqual(c.device, tower_compute_device)
+
+ # The device scope is ignored for variables but not for normal ops.
+ with ops.device('/device:GPU:2'):
+ x = variable_scope.get_variable('x', initializer=10.0)
+ x_add = x.assign_add(c)
+ e = a + c
+ self.assertEqual(
+ device_util.canonicalize(x.device), tower_variable_device)
+ self.assertEqual(x_add.device, x.device)
+ self.assertEqual(e.device, device_util.canonicalize('/device:GPU:2'))
+
+ # The colocate_vars_with can override the distribution's device.
+ with d.colocate_vars_with(x):
+ y = variable_scope.get_variable('y', initializer=20.0)
+ y_add = y.assign_add(x_add)
+ self.assertEqual(
+ device_util.canonicalize(y.device), tower_variable_device)
+ self.assertEqual(y_add.device, y.device)
+ self.assertEqual(y.device, x.device)
+
+ z = variable_scope.get_variable('z', initializer=10.0)
+ self.assertEqual(
+ device_util.canonicalize(z.device), tower_variable_device)
+
+ with ops.control_dependencies([y_add]):
+ z_add = z.assign_add(y)
+ with ops.control_dependencies([z_add]):
+ f = z + c
+ self.assertEqual(f.device, tower_compute_device)
+
+ # The device scope would merge with the default worker device.
+ with ops.device('/CPU:1'):
+ g = e + 1.0
+ self.assertEqual(g.device, device_util.canonicalize('/device:CPU:1'))
+
+ # Ths ops.colocate_with will be ignored when defining a variale but not
+ # for a normal tensor.
+ with ops.colocate_with(x):
+ u = variable_scope.get_variable('u', initializer=30.0)
+ h = f + 1.0
+ self.assertEqual(
+ device_util.canonicalize(u.device), tower_variable_device)
+ self.assertEqual(device_util.canonicalize(x.device), h.device)
+ return y_add, z_add, f
+
+ y, z, f = d.call_for_each_tower(model_fn)
+ self.assertNotEqual(y, None)
+ self.assertNotEqual(z, None)
+ self.assertNotEqual(f, None)
+
+ if context.num_gpus() >= 1 and num_gpus <= 1:
+ variables.global_variables_initializer().run()
+ y_val, z_val, f_val = sess.run([y, z, f])
+ self.assertEqual(y_val, 33.0)
+ self.assertEqual(z_val, 43.0)
+ self.assertEqual(f_val, 46.0)
+
+ def testDeviceAssignmentLocal(self):
+ distribution = parameter_server_strategy.ParameterServerStrategy(
+ num_gpus_per_worker=0)
+ self._test_device_assignment_local(
+ distribution, compute_device='CPU', variable_device='CPU', num_gpus=0)
+
+ distribution = parameter_server_strategy.ParameterServerStrategy(
+ num_gpus_per_worker=1)
+ self._test_device_assignment_local(
+ distribution, compute_device='GPU', variable_device='GPU', num_gpus=1)
+
+ distribution = parameter_server_strategy.ParameterServerStrategy(
+ num_gpus_per_worker=2)
+ self._test_device_assignment_local(
+ distribution, compute_device='GPU', variable_device='CPU', num_gpus=2)
+
+ def _test_simple_increment(self, d, task_type, task_index, master_target):
+ if hasattr(d, '_cluster_spec') and d._cluster_spec:
+ num_workers = len(d._cluster_spec.as_dict().get('worker',
+ ['dummy_worker']))
+ else:
+ num_workers = 1
+ with ops.Graph().as_default(), \
+ self._test_session(target=master_target) as sess, \
+ d.scope():
+
+ def model_fn():
+ x = variable_scope.get_variable('x', initializer=10.0)
+ y = variable_scope.get_variable('y', initializer=20.0)
+
+ x_add = x.assign_add(1.0, use_locking=True)
+ y_add = y.assign_add(1.0, use_locking=True)
+
+ train_op = control_flow_ops.group([x_add, y_add])
+ return x, y, train_op
+
+ x, y, train_op = d.call_for_each_tower(model_fn)
+ train_op = d.group(d.unwrap(train_op))
+
+ if context.num_gpus() < d._num_gpus_per_worker:
+ return True
+
+ if task_index == 0:
+ variables.global_variables_initializer().run()
+
+ # Workers waiting for chief worker's initializing variables.
+ self._init_condition.acquire()
+ self._init_reached += 1
+ while self._init_reached != num_workers:
+ self._init_condition.wait()
+ self._init_condition.notify_all()
+ self._init_condition.release()
+
+ sess.run(train_op)
+
+ # Wait for other workers to finish training.
+ self._finish_condition.acquire()
+ self._finish_reached += 1
+ while self._finish_reached != num_workers:
+ self._finish_condition.wait()
+ self._finish_condition.notify_all()
+ self._finish_condition.release()
+
+ x_val, y_val = sess.run([x, y])
+ self.assertEqual(x_val, 10.0 + 1.0 * num_workers * d.num_towers)
+ self.assertEqual(y_val, 20.0 + 1.0 * num_workers * d.num_towers)
+ return (x_val == 10.0 + 1.0 * num_workers * d.num_towers and
+ y_val == 20.0 + 1.0 * num_workers * d.num_towers)
+
+ def _test_minimize_loss_graph(self, d, task_type, task_index, master_target):
+ with ops.Graph().as_default(), \
+ self._test_session(target=master_target) as sess, \
+ d.scope():
+ l = core.Dense(1, use_bias=False)
+
+ def loss_fn(x):
+ y = array_ops.reshape(l(x), []) - constant_op.constant(1.)
+ return y * y
+
+ # TODO(yuefengz, apassos): eager.backprop.implicit_grad is not safe for
+ # multiple graphs (b/111216820).
+ def grad_fn(x):
+ loss = loss_fn(x)
+ var_list = (
+ variables.trainable_variables() + ops.get_collection(
+ ops.GraphKeys.TRAINABLE_RESOURCE_VARIABLES))
+ grads = gradients.gradients(loss, var_list)
+ ret = list(zip(grads, var_list))
+ return ret
+
+ def update(v, g):
+ return v.assign_sub(0.05 * g, use_locking=True)
+
+ one = d.broadcast(constant_op.constant([[1.]]))
+
+ def step():
+ """Perform one optimization step."""
+ # Run forward & backward to get gradients, variables list.
+ g_v = d.call_for_each_tower(grad_fn, one)
+ # Update the variables using the gradients and the update() function.
+ before_list = []
+ after_list = []
+ for g, v in g_v:
+ fetched = d.read_var(v)
+ before_list.append(fetched)
+ with ops.control_dependencies([fetched]):
+ # TODO(yuefengz): support non-Mirrored variable as destinations.
+ g = d.reduce(
+ variable_scope.VariableAggregation.SUM, g, destinations=v)
+ with ops.control_dependencies(d.unwrap(d.update(v, update, g))):
+ after_list.append(d.read_var(v))
+ return before_list, after_list
+
+ before_out, after_out = step()
+
+ if context.num_gpus() < d._num_gpus_per_worker:
+ return True
+
+ if task_index == 0:
+ variables.global_variables_initializer().run()
+
+ # Workers waiting for chief worker's initializing variables.
+ self._init_condition.acquire()
+ self._init_reached += 1
+ while self._init_reached != 3:
+ self._init_condition.wait()
+ self._init_condition.notify_all()
+ self._init_condition.release()
+
+ for i in range(10):
+ b, a = sess.run((before_out, after_out))
+ if i == 0:
+ before, = b
+ after, = a
+
+ error_before = abs(before - 1)
+ error_after = abs(after - 1)
+ # Error should go down
+ self.assertLess(error_after, error_before)
+ return error_after < error_before
+
+ def _run_client(self, index, model_fn, num_gpus):
+ task_type = run_config.TaskType.WORKER
+ result = model_fn(
+ self._get_ps_distribution_strategy(task_type, index, num_gpus=num_gpus),
+ task_type, index, self._workers[index].target)
+ if result:
+ with self._lock:
+ self._result += 1
+
+ def _run_multiple_clients(self, num_clients, model_fn, num_gpus=0):
+ threads = []
+ for i in range(num_clients):
+ t = threading.Thread(
+ target=self._run_client, args=(i, model_fn, num_gpus))
+ t.start()
+ threads.append(t)
+ for t in threads:
+ t.join()
+
+ def testSimpleBetweenGraph(self):
+ self._run_multiple_clients(3, self._test_simple_increment)
+ self.assertEqual(self._result, 3)
+
+ @combinations.generate(
+ combinations.combine(mode=['graph'], num_gpus=[0, 1, 2]))
+ def testLocalSimpleIncrement(self, num_gpus):
+ d = parameter_server_strategy.ParameterServerStrategy(
+ num_gpus_per_worker=num_gpus)
+ self._test_simple_increment(d, 'dummy_worker', 0, '')
+
+ @combinations.generate(
+ combinations.combine(mode=['graph'], num_gpus=[0, 1, 2]))
+ def testMinimizeLossGraph(self, num_gpus):
+ self._run_multiple_clients(
+ 3, self._test_minimize_loss_graph, num_gpus=num_gpus)
+ self.assertEqual(self._result, 3)
+
+
+if __name__ == '__main__':
+ test.main()
diff --git a/tensorflow/contrib/distribute/python/values.py b/tensorflow/contrib/distribute/python/values.py
index 47dcf679c2..f4da91a8ac 100644
--- a/tensorflow/contrib/distribute/python/values.py
+++ b/tensorflow/contrib/distribute/python/values.py
@@ -210,6 +210,11 @@ class DistributedVariable(DistributedDelegate):
# without this it will use `__getattr__` which will delegate to a component
# variable.
self._keras_initialized = False
+ # Typically, a `DistributedVariable`'s initializer is composed of the
+ # initializers of the components variables. However, in some cases, such as
+ # when restoring from a checkpoint, we may set the _initializer_op
+ # property on the entire `DistributedVariable`.
+ self._initializer_op = None
super(DistributedVariable, self).__init__(index)
def is_initialized(self, name=None):
@@ -239,9 +244,14 @@ class DistributedVariable(DistributedDelegate):
@property
def initializer(self):
- # return grouped ops of all the var initializations of component values of
- # the mirrored variable
- return control_flow_ops.group([v.initializer for v in self._index.values()])
+ if self._initializer_op:
+ init_op = self._initializer_op
+ else:
+ # return grouped ops of all the var initializations of component values of
+ # the mirrored variable
+ init_op = control_flow_ops.group(
+ [v.initializer for v in self._index.values()])
+ return init_op
@property
def graph(self):
@@ -284,6 +294,9 @@ class DistributedVariable(DistributedDelegate):
self._primary_var.op.type)
return self.get().op
+ def read_value(self):
+ return distribute_lib.get_distribution_strategy().read_var(self)
+
def _should_act_as_resource_variable(self):
"""Pass resource_variable_ops.is_resource_variable check."""
pass
diff --git a/tensorflow/contrib/distributions/python/ops/quantized_distribution.py b/tensorflow/contrib/distributions/python/ops/quantized_distribution.py
index ef3bdfa75f..18a0f754e6 100644
--- a/tensorflow/contrib/distributions/python/ops/quantized_distribution.py
+++ b/tensorflow/contrib/distributions/python/ops/quantized_distribution.py
@@ -326,6 +326,21 @@ class QuantizedDistribution(distributions.Distribution):
graph_parents=graph_parents,
name=name)
+ @property
+ def distribution(self):
+ """Base distribution, p(x)."""
+ return self._dist
+
+ @property
+ def low(self):
+ """Lowest value that quantization returns."""
+ return self._low
+
+ @property
+ def high(self):
+ """Highest value that quantization returns."""
+ return self._high
+
def _batch_shape_tensor(self):
return self.distribution.batch_shape_tensor()
@@ -569,8 +584,3 @@ class QuantizedDistribution(distributions.Distribution):
dependencies = [distribution_util.assert_integer_form(
value, message="value has non-integer components.")]
return control_flow_ops.with_dependencies(dependencies, value)
-
- @property
- def distribution(self):
- """Base distribution, p(x)."""
- return self._dist
diff --git a/tensorflow/contrib/eager/python/examples/densenet/densenet_graph_test.py b/tensorflow/contrib/eager/python/examples/densenet/densenet_graph_test.py
index bd0057fb1a..4b3cb624bc 100644
--- a/tensorflow/contrib/eager/python/examples/densenet/densenet_graph_test.py
+++ b/tensorflow/contrib/eager/python/examples/densenet/densenet_graph_test.py
@@ -128,8 +128,10 @@ class DensenetBenchmark(tf.test.Benchmark):
weight_decay=1e-4, dropout_rate=0,
pool_initial=True, include_top=True)
logits = model(images, training=True)
- loss = tf.losses.softmax_cross_entropy(
+ cross_ent = tf.losses.softmax_cross_entropy(
logits=logits, onehot_labels=labels)
+ regularization = tf.add_n(model.losses)
+ loss = cross_ent + regularization
optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0)
train_op = optimizer.minimize(loss)
diff --git a/tensorflow/contrib/eager/python/examples/densenet/densenet_test.py b/tensorflow/contrib/eager/python/examples/densenet/densenet_test.py
index 4f19711fb8..0736ed02b7 100644
--- a/tensorflow/contrib/eager/python/examples/densenet/densenet_test.py
+++ b/tensorflow/contrib/eager/python/examples/densenet/densenet_test.py
@@ -98,12 +98,52 @@ class DensenetTest(tf.test.TestCase):
output_shape = model(rand_input).shape
self.assertEqual(output_shape, (batch_size, output_classes))
+ def test_regularization(self):
+ if tf.test.is_gpu_available():
+ rand_input = tf.random_uniform((10, 3, 32, 32))
+ data_format = 'channels_first'
+ else:
+ rand_input = tf.random_uniform((10, 32, 32, 3))
+ data_format = 'channels_last'
+ weight_decay = 1e-4
+
+ conv = tf.keras.layers.Conv2D(
+ 3, (3, 3),
+ padding='same',
+ use_bias=False,
+ data_format=data_format,
+ kernel_regularizer=tf.keras.regularizers.l2(weight_decay))
+ optimizer = tf.train.GradientDescentOptimizer(0.1)
+ conv(rand_input) # Initialize the variables in the layer
+
+ def compute_true_l2(vs, wd):
+ return tf.reduce_sum(tf.square(vs)) * wd
+
+ true_l2 = compute_true_l2(conv.variables, weight_decay)
+ keras_l2 = tf.add_n(conv.losses)
+ self.assertAllClose(true_l2, keras_l2)
+
+ with tf.GradientTape() as tape_true, tf.GradientTape() as tape_keras:
+ loss = tf.reduce_sum(conv(rand_input))
+ loss_with_true_l2 = loss + compute_true_l2(conv.variables, weight_decay)
+ loss_with_keras_l2 = loss + tf.add_n(conv.losses)
+
+ true_grads = tape_true.gradient(loss_with_true_l2, conv.variables)
+ keras_grads = tape_keras.gradient(loss_with_keras_l2, conv.variables)
+ self.assertAllClose(true_grads, keras_grads)
+
+ optimizer.apply_gradients(zip(keras_grads, conv.variables))
+ keras_l2_after_update = tf.add_n(conv.losses)
+ self.assertNotAllClose(keras_l2, keras_l2_after_update)
+
def compute_gradients(model, images, labels):
with tf.GradientTape() as tape:
logits = model(images, training=True)
- loss = tf.losses.softmax_cross_entropy(
+ cross_ent = tf.losses.softmax_cross_entropy(
logits=logits, onehot_labels=labels)
+ regularization = tf.add_n(model.losses)
+ loss = cross_ent + regularization
tf.contrib.summary.scalar(name='loss', tensor=loss)
return tape.gradient(loss, model.variables)
diff --git a/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb b/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb
index 6be09f98df..b173f856c6 100644
--- a/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb
+++ b/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb
@@ -1,31 +1,11 @@
{
- "nbformat": 4,
- "nbformat_minor": 0,
- "metadata": {
- "colab": {
- "name": "text_generation.ipynb",
- "version": "0.3.2",
- "views": {},
- "default_view": {},
- "provenance": [],
- "private_outputs": true,
- "collapsed_sections": [],
- "toc_visible": true
- },
- "kernelspec": {
- "display_name": "Python 3",
- "language": "python",
- "name": "python3"
- },
- "accelerator": "GPU"
- },
"cells": [
{
+ "cell_type": "markdown",
"metadata": {
- "id": "hcD2nPQvPOFM",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "hcD2nPQvPOFM"
},
- "cell_type": "markdown",
"source": [
"##### Copyright 2018 The TensorFlow Authors.\n",
"\n",
@@ -33,19 +13,19 @@
"\n",
"# Text Generation using a RNN\n",
"\n",
- "<table class=\"tfo-notebook-buttons\" align=\"left\"><td>\n",
- "<a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tensorflow/blob/master/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb\">\n",
- " <img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Run in Google Colab</a> \n",
- "</td><td>\n",
- "<a target=\"_blank\" href=\"https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb\"><img width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />View source on Github</a></td></table>"
+ "\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\u003ctd\u003e\n",
+ "\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tensorflow/blob/master/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb\"\u003e\n",
+ " \u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" /\u003eRun in Google Colab\u003c/a\u003e \n",
+ "\u003c/td\u003e\u003ctd\u003e\n",
+ "\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/eager/python/examples/generative_examples/text_generation.ipynb\"\u003e\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" /\u003eView source on Github\u003c/a\u003e\u003c/td\u003e\u003c/table\u003e"
]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "BwpJ5IffzRG6",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "BwpJ5IffzRG6"
},
- "cell_type": "markdown",
"source": [
"This notebook demonstrates how to generate text using an RNN using [tf.keras](https://www.tensorflow.org/programmers_guide/keras) and [eager execution](https://www.tensorflow.org/programmers_guide/eager). If you like, you can write a similar [model](https://github.com/fchollet/deep-learning-with-python-notebooks/blob/master/8.1-text-generation-with-lstm.ipynb) using less code. Here, we show a lower-level impementation that's useful to understand as prework before diving in to deeper examples in a similar, like [Neural Machine Translation with Attention](https://colab.research.google.com/github/tensorflow/tensorflow/blob/master/tensorflow/contrib/eager/python/examples/nmt_with_attention/nmt_with_attention.ipynb).\n",
"\n",
@@ -102,58 +82,60 @@
]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "R3p22DBDsaCA",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "R3p22DBDsaCA"
},
- "cell_type": "markdown",
"source": [
"## Install unidecode library\n",
"A helpful library to convert unicode to ASCII."
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "wZ6LOM12wKGH",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "wZ6LOM12wKGH"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"!pip install unidecode"
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "WGyKZj3bzf9p",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "WGyKZj3bzf9p"
},
- "cell_type": "markdown",
"source": [
"## Import tensorflow and enable eager execution."
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "yG_n40gFzf9s",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "yG_n40gFzf9s"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
- "# Import TensorFlow >= 1.9 and enable eager execution\n",
+ "# Import TensorFlow \u003e= 1.9 and enable eager execution\n",
"import tensorflow as tf\n",
"\n",
"# Note: Once you enable eager execution, it cannot be disabled. \n",
@@ -164,16 +146,14 @@
"import random\n",
"import unidecode\n",
"import time"
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "EHDoRoc5PKWz",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "EHDoRoc5PKWz"
},
- "cell_type": "markdown",
"source": [
"## Download the dataset\n",
"\n",
@@ -182,76 +162,78 @@
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "pD_55cOxLkAb",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "pD_55cOxLkAb"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
- "path_to_file = tf.keras.utils.get_file('shakespeare.txt', 'https://storage.googleapis.com/yashkatariya/shakespeare.txt')"
- ],
- "execution_count": 0,
- "outputs": []
+ "path_to_file = tf.keras.utils.get_file('shakespeare.txt', 'https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt')"
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "UHjdCjDuSvX_",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "UHjdCjDuSvX_"
},
- "cell_type": "markdown",
"source": [
"## Read the dataset\n",
"\n"
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "-E5JvY3wzf94",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "-E5JvY3wzf94"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"text = unidecode.unidecode(open(path_to_file).read())\n",
"# length of text is the number of characters in it\n",
"print (len(text))"
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "Il9ww98izf-D",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "Il9ww98izf-D"
},
- "cell_type": "markdown",
"source": [
"Creating dictionaries to map from characters to their indices and vice-versa, which will be used to vectorize the inputs"
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "IalZLbvOzf-F",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "IalZLbvOzf-F"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"# unique contains all the unique characters in the file\n",
"unique = sorted(set(text))\n",
@@ -259,22 +241,22 @@
"# creating a mapping from unique characters to indices\n",
"char2idx = {u:i for i, u in enumerate(unique)}\n",
"idx2char = {i:u for i, u in enumerate(unique)}"
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "1v_qUYfAzf-I",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "1v_qUYfAzf-I"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"# setting the maximum length sentence we want for a single input in characters\n",
"max_length = 100\n",
@@ -293,16 +275,14 @@
"\n",
"# buffer size to shuffle our dataset\n",
"BUFFER_SIZE = 10000"
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "LFjSVAlWzf-N",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "LFjSVAlWzf-N"
},
- "cell_type": "markdown",
"source": [
"## Creating the input and output tensors\n",
"\n",
@@ -319,17 +299,19 @@
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "0UHJDA39zf-O",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "0UHJDA39zf-O"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"input_text = []\n",
"target_text = []\n",
@@ -343,45 +325,43 @@
" \n",
"print (np.array(input_text).shape)\n",
"print (np.array(target_text).shape)"
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "MJdfPmdqzf-R",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "MJdfPmdqzf-R"
},
- "cell_type": "markdown",
"source": [
"## Creating batches and shuffling them using tf.data"
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "p2pGotuNzf-S",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "p2pGotuNzf-S"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"dataset = tf.data.Dataset.from_tensor_slices((input_text, target_text)).shuffle(BUFFER_SIZE)\n",
"dataset = dataset.apply(tf.contrib.data.batch_and_drop_remainder(BATCH_SIZE))"
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "m8gPwEjRzf-Z",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "m8gPwEjRzf-Z"
},
- "cell_type": "markdown",
"source": [
"## Creating the model\n",
"\n",
@@ -393,17 +373,19 @@
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "P3KTiiInzf-a",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "P3KTiiInzf-a"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"class Model(tf.keras.Model):\n",
" def __init__(self, vocab_size, embedding_dim, units, batch_size):\n",
@@ -447,66 +429,64 @@
" x = self.fc(output)\n",
"\n",
" return x, states"
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "trpqTWyvk0nr",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "trpqTWyvk0nr"
},
- "cell_type": "markdown",
"source": [
"## Call the model and set the optimizer and the loss function"
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "7t2XrzEOzf-e",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "7t2XrzEOzf-e"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"model = Model(vocab_size, embedding_dim, units, BATCH_SIZE)"
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "dkjWIATszf-h",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "dkjWIATszf-h"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"optimizer = tf.train.AdamOptimizer()\n",
"\n",
"# using sparse_softmax_cross_entropy so that we don't have to create one-hot vectors\n",
"def loss_function(real, preds):\n",
" return tf.losses.sparse_softmax_cross_entropy(labels=real, logits=preds)"
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "lPrP0XMUzf-p",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "lPrP0XMUzf-p"
},
- "cell_type": "markdown",
"source": [
"## Train the model\n",
"\n",
@@ -531,17 +511,19 @@
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "d4tSNwymzf-q",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "d4tSNwymzf-q"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"# Training step\n",
"\n",
@@ -574,16 +556,14 @@
" \n",
" print ('Epoch {} Loss {:.4f}'.format(epoch+1, loss))\n",
" print('Time taken for 1 epoch {} sec\\n'.format(time.time() - start))"
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "DjGz1tDkzf-u",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "DjGz1tDkzf-u"
},
- "cell_type": "markdown",
"source": [
"## Predicting using our trained model\n",
"\n",
@@ -601,17 +581,19 @@
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "WvuwZBX5Ogfd",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "WvuwZBX5Ogfd"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"# Evaluation step(generating text using the model learned)\n",
"\n",
@@ -648,16 +630,14 @@
" text_generated += idx2char[predicted_id]\n",
"\n",
"print (start_string + text_generated)"
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "AM2Uma_-yVIq",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "AM2Uma_-yVIq"
},
- "cell_type": "markdown",
"source": [
"## Next steps\n",
"\n",
@@ -668,22 +648,42 @@
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "gtEd86sX5cB2",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "gtEd86sX5cB2"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
""
- ],
- "execution_count": 0,
- "outputs": []
+ ]
}
- ]
+ ],
+ "metadata": {
+ "accelerator": "GPU",
+ "colab": {
+ "collapsed_sections": [],
+ "default_view": {},
+ "name": "text_generation.ipynb",
+ "private_outputs": true,
+ "provenance": [],
+ "toc_visible": true,
+ "version": "0.3.2",
+ "views": {}
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
}
diff --git a/tensorflow/contrib/eager/python/examples/l2hmc/README.md b/tensorflow/contrib/eager/python/examples/l2hmc/README.md
index d6a2ff7558..f171806e37 100644
--- a/tensorflow/contrib/eager/python/examples/l2hmc/README.md
+++ b/tensorflow/contrib/eager/python/examples/l2hmc/README.md
@@ -4,16 +4,15 @@ This folder contains an implementation of [L2HMC](https://arxiv.org/pdf/1711.092
With eager execution enabled, longer sample chains can be handled compared to graph mode, since no graph is explicitly stored. Moreover, with eager execution enabled, there is no need to use a `tf.while_loop`.
## What is L2HMC?
-L2HMC is an algorithm that learns a non-volume preserving transformation
-for an HMC-like sampling algorithm. More specifically, the non-volume preserving
+L2HMC is an adaptive Markov Chain Monte Carlo (MCMC) algorithm that learns a non-volume preserving transformation
+for a Hamiltonian Monte Carlo (HMC) sampling algorithm. More specifically, the non-volume preserving
transformation is learned with neural nets instantiated within Normalizing Flows
-(more precisely, real-NVPs).
+(real-NVPs).
## Content
- `l2hmc.py`: Dynamics definitions and example energy functions,
-including the 2D strongly correlated Gaussian, the rough well energy function,
-and a Gaussian mixture model.
+including the 2D strongly correlated Gaussian and the rough well energy function,
- `l2hmc_test.py`: Unit tests and benchmarks for training a sampler on the energy functions in both eager and graph mode.
- `neural_nets.py`: The neural net for learning the kernel on the 2D strongly correlated example.
- `main.py`: Run to train a samplers on 2D energy landscapes.
@@ -32,7 +31,7 @@ tensorboard and a plot of sampled chain from the trained sampler.
Specifying the optional argument `use_defun` will let the program use compiled
graphs when running specific sections and improve the overall speed.
-## Boosting Performance with `defun`
+## Boosting Performance with `tfe.defun`
Currently, some models may experience increased overhead with eager execution enabled.
To improve performance, we could wrap certain functions with the decorator `@tfe.defun`.
For example, we could wrap the function that does the sampling step:
diff --git a/tensorflow/contrib/eager/python/examples/nmt_with_attention/nmt_with_attention.ipynb b/tensorflow/contrib/eager/python/examples/nmt_with_attention/nmt_with_attention.ipynb
index 1f66d7e752..1ab1b71bd0 100644
--- a/tensorflow/contrib/eager/python/examples/nmt_with_attention/nmt_with_attention.ipynb
+++ b/tensorflow/contrib/eager/python/examples/nmt_with_attention/nmt_with_attention.ipynb
@@ -383,6 +383,7 @@
"source": [
"BUFFER_SIZE = len(input_tensor_train)\n",
"BATCH_SIZE = 64\n",
+ "N_BATCH = BUFFER_SIZE//BATCH_SIZE\n",
"embedding_dim = 256\n",
"units = 1024\n",
"vocab_inp_size = len(inp_lang.word2idx)\n",
@@ -677,21 +678,23 @@
" # using teacher forcing\n",
" dec_input = tf.expand_dims(targ[:, t], 1)\n",
" \n",
- " total_loss += (loss / int(targ.shape[1]))\n",
+ " batch_loss = (loss / int(targ.shape[1]))\n",
+ " \n",
+ " total_loss += batch_loss\n",
" \n",
" variables = encoder.variables + decoder.variables\n",
" \n",
" gradients = tape.gradient(loss, variables)\n",
- " \n",
+ " \n",
" optimizer.apply_gradients(zip(gradients, variables), tf.train.get_or_create_global_step())\n",
- "\n",
+ " \n",
" if batch % 100 == 0:\n",
" print('Epoch {} Batch {} Loss {:.4f}'.format(epoch + 1,\n",
" batch,\n",
- " loss.numpy() / int(targ.shape[1])))\n",
+ " batch_loss.numpy()))\n",
" \n",
" print('Epoch {} Loss {:.4f}'.format(epoch + 1,\n",
- " total_loss/len(input_tensor)))\n",
+ " total_loss / N_BATCH))\n",
" print('Time taken for 1 epoch {} sec\\n'.format(time.time() - start))"
],
"execution_count": 0,
@@ -906,4 +909,4 @@
]
}
]
-} \ No newline at end of file
+}
diff --git a/tensorflow/contrib/eager/python/examples/notebooks/automatic_differentiation.ipynb b/tensorflow/contrib/eager/python/examples/notebooks/automatic_differentiation.ipynb
index 7c0f9b5b81..51b7ffc4de 100644
--- a/tensorflow/contrib/eager/python/examples/notebooks/automatic_differentiation.ipynb
+++ b/tensorflow/contrib/eager/python/examples/notebooks/automatic_differentiation.ipynb
@@ -1,46 +1,30 @@
{
- "nbformat": 4,
- "nbformat_minor": 0,
- "metadata": {
- "colab": {
- "name": "automatic_differentiation.ipynb",
- "version": "0.3.2",
- "views": {},
- "default_view": {},
- "provenance": [],
- "private_outputs": true,
- "collapsed_sections": [],
- "toc_visible": true
- },
- "kernelspec": {
- "name": "python3",
- "display_name": "Python 3"
- }
- },
"cells": [
{
+ "cell_type": "markdown",
"metadata": {
- "id": "t09eeeR5prIJ",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "t09eeeR5prIJ"
},
- "cell_type": "markdown",
"source": [
"##### Copyright 2018 The TensorFlow Authors."
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "GCCk8_dHpuNf",
- "colab_type": "code",
+ "cellView": "form",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
},
- "cellView": "form"
+ "colab_type": "code",
+ "id": "GCCk8_dHpuNf"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"# you may not use this file except in compliance with the License.\n",
@@ -53,81 +37,79 @@
"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"# See the License for the specific language governing permissions and\n",
"# limitations under the License."
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "xh8WkEwWpnm7",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "xh8WkEwWpnm7"
},
- "cell_type": "markdown",
"source": [
"# Automatic differentiation and gradient tape"
]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "idv0bPeCp325",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "idv0bPeCp325"
},
- "cell_type": "markdown",
"source": [
- "<table class=\"tfo-notebook-buttons\" align=\"left\"><td>\n",
- "<a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tensorflow/blob/master/tensorflow/contrib/eager/python/examples/notebooks/automatic_differentiation.ipynb\">\n",
- " <img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Run in Google Colab</a>\n",
- "</td><td>\n",
- "<a target=\"_blank\" href=\"https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/eager/python/examples/notebooks/automatic_differentiation.ipynb\"><img width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />View source on GitHub</a></td></table>"
+ "\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\u003ctd\u003e\n",
+ "\u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/tensorflow/blob/master/tensorflow/contrib/eager/python/examples/notebooks/automatic_differentiation.ipynb\"\u003e\n",
+ " \u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" /\u003eRun in Google Colab\u003c/a\u003e\n",
+ "\u003c/td\u003e\u003ctd\u003e\n",
+ "\u003ca target=\"_blank\" href=\"https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/eager/python/examples/notebooks/automatic_differentiation.ipynb\"\u003e\u003cimg width=32px src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" /\u003eView source on GitHub\u003c/a\u003e\u003c/td\u003e\u003c/table\u003e"
]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "vDJ4XzMqodTy",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "vDJ4XzMqodTy"
},
- "cell_type": "markdown",
"source": [
"In the previous tutorial we introduced `Tensor`s and operations on them. In this tutorial we will cover [automatic differentiation](https://en.wikipedia.org/wiki/Automatic_differentiation), a key technique for optimizing machine learning models."
]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "GQJysDM__Qb0",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "GQJysDM__Qb0"
},
- "cell_type": "markdown",
"source": [
"## Setup\n"
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "OiMPZStlibBv",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "OiMPZStlibBv"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"import tensorflow as tf\n",
"tf.enable_eager_execution()\n",
"\n",
"tfe = tf.contrib.eager # Shorthand for some symbols"
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "1CLWJl0QliB0",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "1CLWJl0QliB0"
},
- "cell_type": "markdown",
"source": [
"## Derivatives of a function\n",
"\n",
@@ -135,17 +117,19 @@
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "9FViq92UX7P8",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "9FViq92UX7P8"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"from math import pi\n",
"\n",
@@ -159,17 +143,15 @@
"# with respect to its arguments. Since f() has a single argument,\n",
"# grad_f will return a list with a single element.\n",
"grad_f = tfe.gradients_function(f)\n",
- "assert tf.abs(grad_f(pi/2)[0]).numpy() < 1e-7"
- ],
- "execution_count": 0,
- "outputs": []
+ "assert tf.abs(grad_f(pi/2)[0]).numpy() \u003c 1e-7"
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "v9fPs8RyopCf",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "v9fPs8RyopCf"
},
- "cell_type": "markdown",
"source": [
"### Higher-order gradients\n",
"\n",
@@ -177,17 +159,19 @@
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "3D0ZvnGYo0rW",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "3D0ZvnGYo0rW"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"def f(x):\n",
" return tf.square(tf.sin(x))\n",
@@ -205,16 +189,14 @@
"plt.plot(x, grad(grad(grad(f)))(x), label=\"third derivative\")\n",
"plt.legend()\n",
"plt.show()"
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "-39gouo7mtgu",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "-39gouo7mtgu"
},
- "cell_type": "markdown",
"source": [
"## Gradient tapes\n",
"\n",
@@ -225,21 +207,25 @@
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "MH0UfjympWf7",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "MH0UfjympWf7"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"def f(x, y):\n",
" output = 1\n",
- " for i in range(y):\n",
+ " # Must use range(int(y)) instead of range(y) in Python 3 when\n",
+ " # using TensorFlow 1.10 and earlier. Can use range(y) in 1.11+\n",
+ " for i in range(int(y)):\n",
" output = tf.multiply(output, x)\n",
" return output\n",
"\n",
@@ -251,16 +237,14 @@
"assert g(3.0, 2).numpy() == 6.0 # And its gradient will be 2 * x\n",
"assert f(4.0, 3).numpy() == 64.0 # f(x, 3) is essentially x * x * x\n",
"assert g(4.0, 3).numpy() == 48.0 # And its gradient will be 3 * x * x"
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "aNmR5-jhpX2t",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "aNmR5-jhpX2t"
},
- "cell_type": "markdown",
"source": [
"At times it may be inconvenient to encapsulate computation of interest into a function. For example, if you want the gradient of the output with respect to intermediate values computed in the function. In such cases, the slightly more verbose but explicit [tf.GradientTape](https://www.tensorflow.org/api_docs/python/tf/GradientTape) context is useful. All computation inside the context of a `tf.GradientTape` is \"recorded\".\n",
"\n",
@@ -268,17 +252,19 @@
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "bAFeIE8EuVIq",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "bAFeIE8EuVIq"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"x = tf.ones((2, 2))\n",
" \n",
@@ -300,16 +286,14 @@
"for i in [0, 1]:\n",
" for j in [0, 1]:\n",
" assert dz_dx[i][j].numpy() == 8.0"
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "DK05KXrAAld3",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "DK05KXrAAld3"
},
- "cell_type": "markdown",
"source": [
"### Higher-order gradients\n",
"\n",
@@ -317,17 +301,19 @@
]
},
{
+ "cell_type": "code",
+ "execution_count": 0,
"metadata": {
- "id": "cPQgthZ7ugRJ",
- "colab_type": "code",
"colab": {
"autoexec": {
"startup": false,
"wait_interval": 0
}
- }
+ },
+ "colab_type": "code",
+ "id": "cPQgthZ7ugRJ"
},
- "cell_type": "code",
+ "outputs": [],
"source": [
"# TODO(ashankar): Should we use the persistent tape here instead? Follow up on Tom and Alex's discussion\n",
"\n",
@@ -344,21 +330,37 @@
"\n",
"assert dy_dx.numpy() == 3.0\n",
"assert d2y_dx2.numpy() == 6.0"
- ],
- "execution_count": 0,
- "outputs": []
+ ]
},
{
+ "cell_type": "markdown",
"metadata": {
- "id": "4U1KKzUpNl58",
- "colab_type": "text"
+ "colab_type": "text",
+ "id": "4U1KKzUpNl58"
},
- "cell_type": "markdown",
"source": [
"## Next Steps\n",
"\n",
"In this tutorial we covered gradient computation in TensorFlow. With that we have enough of the primitives required to build an train neural networks, which we will cover in the [next tutorial](https://github.com/tensorflow/models/tree/master/official/contrib/eager/python/examples/notebooks/3_neural_networks.ipynb)."
]
}
- ]
-} \ No newline at end of file
+ ],
+ "metadata": {
+ "colab": {
+ "collapsed_sections": [],
+ "default_view": {},
+ "name": "automatic_differentiation.ipynb",
+ "private_outputs": true,
+ "provenance": [],
+ "toc_visible": true,
+ "version": "0.3.2",
+ "views": {}
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "name": "python3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 0
+}
diff --git a/tensorflow/contrib/eager/python/examples/revnet/BUILD b/tensorflow/contrib/eager/python/examples/revnet/BUILD
index 3316dc1114..4f0d46b1ba 100644
--- a/tensorflow/contrib/eager/python/examples/revnet/BUILD
+++ b/tensorflow/contrib/eager/python/examples/revnet/BUILD
@@ -43,6 +43,27 @@ py_library(
],
)
+py_library(
+ name = "resnet_preprocessing",
+ srcs = ["resnet_preprocessing.py"],
+ srcs_version = "PY2AND3",
+ tags = ["local"],
+ deps = [
+ "//tensorflow:tensorflow_py",
+ ],
+)
+
+py_library(
+ name = "imagenet_input",
+ srcs = ["imagenet_input.py"],
+ srcs_version = "PY2AND3",
+ tags = ["local"],
+ deps = [
+ ":resnet_preprocessing",
+ "//tensorflow:tensorflow_py",
+ ],
+)
+
# Tests
cuda_py_test(
name = "ops_test",
diff --git a/tensorflow/contrib/eager/python/examples/revnet/README.md b/tensorflow/contrib/eager/python/examples/revnet/README.md
index 21fc44febc..2875d0ffb3 100644
--- a/tensorflow/contrib/eager/python/examples/revnet/README.md
+++ b/tensorflow/contrib/eager/python/examples/revnet/README.md
@@ -1,18 +1,21 @@
# RevNet with TensorFlow eager execution
-This folder contains an TensorFlow eager implementation of the [Reversible Residual Network](https://arxiv.org/pdf/1707.04585.pdf) adapted from the released implementation by the authors. The presented implementation can be ran both in eager and graph mode. The code is considerably simplified with `tf.GradientTape`. Moreover, we reduce the step of reconstructing the outputs. This saves us from using `tf.stop_gradient` and makes the model run faster.
+This folder contains a TensorFlow eager implementation of the [Reversible Residual Network](https://arxiv.org/pdf/1707.04585.pdf) adapted from the released implementation by the authors. The presented implementation can be ran both in eager and graph mode. The code is considerably simplified with `tf.GradientTape`. Moreover, we reduce the step of reconstructing the outputs. This saves us from using `tf.stop_gradient` and makes the model run faster.
## Content
- `revnet.py`: The RevNet model.
- `blocks.py`: The relevant reversible blocks.
+- `ops.py`: Auxiliary downsampling operation.
- `cifar_tfrecords.py`: Script to generate the TFRecords for both CIFAR-10 and CIFAR-100.
- `cifar_input.py`: Script to read from TFRecords and generate dataset objects with the `tf.data` API.
- `config.py`: Configuration file for network architectures and training hyperparameters.
- `main.py`: Main training and evaluation script.
-- `ops.py`: Auxiliary downsampling operation.
+- `main_estimator.py`: Script to train RevNet models on CIFAR-10 and CIFAR-100 with the `tf.estimator` API.
+- `main_estimator_tpu.py`: Script to train RevNet models on ImageNet with TPU estimators on Cloud TPUs.
+- `resnet_preprocessing.py`, `imagenet_input.py`: Boilerplate to read ImageNet data from TFRecords.
-## To run
+## Train on CIFAR-10/CIFAR-100
- Make sure you have installed TensorFlow 1.9+ or the latest `tf-nightly`
or `tf-nightly-gpu` pip package in order to access the eager execution feature.
@@ -24,7 +27,7 @@ python cifar_tfrecords.py --data_dir ${PWD}/cifar
to download the cifar dataset and convert them
to TFRecords. This produces TFRecord files for both CIFAR-10 and CIFAR-100.
-- To train a model run
+- To train a model, run
```bash
python main.py --data_dir ${PWD}/cifar
@@ -34,8 +37,63 @@ python main.py --data_dir ${PWD}/cifar
- `train_dir`: Directory to store eventfiles and checkpoints.
- `restore`: Restore the latest checkpoint.
- `validate`: Use validation set for training monitoring.
- - `manual_grad`: Use the manually defined gradient map given by the authors.
- - `dataset`: Use either `cifar-10` or `cifar-100`
+ - `dataset`: Use either `cifar-10` or `cifar-100`.
+ - `config`: RevNet configuration.
+ - `use_defun`: Use `tfe.defun` to boost performance.
+
+- To train a model with estimators in graph-mode, run
+
+```bash
+python main_estimator.py --data_dir ${PWD}/cifar
+```
+
+- Optional arguments for `main.py` include
+ - `model_dir`: Directory to store eventfiles and checkpoints.
+ - `dataset`: Use either `cifar-10` or `cifar-100`.
+ - `config`: RevNet configuration.
+ - `export`: Export the model for serving if True.
+
+## Speed up with `tfe.defun`
+Even though the speed difference between pure eager execution and graph-mode execution is noticeable,
+the difference between fully "defunned" model training and graph-mode
+training is negligible.
+
+## Train on ImageNet with Cloud TPUs
+The standard way to train models on Cloud TPUs is via TPU estimators and graph-mode
+execution. Models built with the `tf.keras` API are fully compatible with TPU estimators.
+
+### Setup a Google Cloud project
+
+Follow the instructions at the [Quickstart Guide](https://cloud.google.com/tpu/docs/quickstart)
+to get a GCE VM with access to Cloud TPU.
+
+To run this model, you will need:
+
+* A GCE VM instance with an associated Cloud TPU resource
+* A GCS bucket to store your training checkpoints
+* (Optional): The ImageNet training and validation data preprocessed into
+ TFRecord format, and stored in GCS.
+
+### Format the data
+
+The data is expected to be formatted in TFRecord format, as generated by [this
+script](https://github.com/tensorflow/tpu/blob/master/tools/datasets/imagenet_to_gcs.py).
+
+If you do not have ImageNet dataset prepared, you can use a randomly generated
+fake dataset to test the model. It is located at
+`gs://cloud-tpu-test-datasets/fake_imagenet`.
+
+### Start training
+
+Train the model by executing the following command (substituting the appropriate
+values):
+
+```bash
+python main_estimator_tpu.py \
+ --tpu=$TPU_NAME \
+ --data_dir=$DATA_DIR \
+ --model_dir=$MODEL_DIR
+```
## Performance
- With the current implementation, RevNet-38 achieves >92% on CIFAR-10 and >71% on CIFAR-100.
diff --git a/tensorflow/contrib/eager/python/examples/revnet/blocks.py b/tensorflow/contrib/eager/python/examples/revnet/blocks.py
index 8a530b0d71..f61354bc38 100644
--- a/tensorflow/contrib/eager/python/examples/revnet/blocks.py
+++ b/tensorflow/contrib/eager/python/examples/revnet/blocks.py
@@ -91,32 +91,21 @@ class RevBlock(tf.keras.Model):
h = block(h, training=training)
return h
- def backward_grads_and_vars(self, x, y, dy, training=True):
+ def backward_grads(self, x, y, dy, training=True):
"""Apply reversible block backward to outputs."""
grads_all = []
- vars_all = []
-
for i in reversed(range(len(self.blocks))):
block = self.blocks[i]
if i == 0:
# First block usually contains downsampling that can't be reversed
- with tf.GradientTape() as tape:
- tape.watch(x)
- y = block(x, training=training)
-
- grads_combined = tape.gradient(
- y, [x] + block.trainable_variables, output_gradients=dy)
- dy = grads_combined[0]
- grads_all += grads_combined[1:]
- vars_all += block.trainable_variables
+ dy, grads = block.backward_grads_with_downsample(
+ x, y, dy, training=True)
else:
- y, dy, grads, vars_ = block.backward_grads_and_vars(
- y, dy, training=training)
- grads_all += grads
- vars_all += vars_
+ y, dy, grads = block.backward_grads(y, dy, training=training)
+ grads_all = grads + grads_all
- return dy, grads_all, vars_all
+ return dy, grads_all
class _Residual(tf.keras.Model):
@@ -178,10 +167,9 @@ class _Residual(tf.keras.Model):
fused=fused,
dtype=dtype)
- def call(self, x, training=True, concat=True):
+ def call(self, x, training=True):
"""Apply residual block to inputs."""
-
- x1, x2 = tf.split(x, num_or_size_splits=2, axis=self.axis)
+ x1, x2 = x
f_x2 = self.f(x2, training=training)
x1_down = ops.downsample(
x1, self.filters // 2, self.strides, axis=self.axis)
@@ -190,42 +178,81 @@ class _Residual(tf.keras.Model):
y1 = f_x2 + x1_down
g_y1 = self.g(y1, training=training)
y2 = g_y1 + x2_down
- if not concat: # For correct backward grads
- return y1, y2
- return tf.concat([y1, y2], axis=self.axis)
+ return y1, y2
- def backward_grads_and_vars(self, y, dy, training=True):
+ def backward_grads(self, y, dy, training=True):
"""Manually compute backward gradients given input and output grads."""
- dy1, dy2 = tf.split(dy, num_or_size_splits=2, axis=self.axis)
+ dy1, dy2 = dy
+ y1, y2 = y
- with tf.GradientTape(persistent=True) as tape:
- tape.watch(y)
- y1, y2 = tf.split(y, num_or_size_splits=2, axis=self.axis)
+ with tf.GradientTape() as gtape:
+ gtape.watch(y1)
gy1 = self.g(y1, training=training)
+ grads_combined = gtape.gradient(
+ gy1, [y1] + self.g.trainable_variables, output_gradients=dy2)
+ dg = grads_combined[1:]
+ dx1 = dy1 + grads_combined[0]
+ # This doesn't affect eager execution, but improves memory efficiency with
+ # graphs
+ with tf.control_dependencies(dg + [dx1]):
x2 = y2 - gy1
+
+ with tf.GradientTape() as ftape:
+ ftape.watch(x2)
fx2 = self.f(x2, training=training)
+ grads_combined = ftape.gradient(
+ fx2, [x2] + self.f.trainable_variables, output_gradients=dx1)
+ df = grads_combined[1:]
+ dx2 = dy2 + grads_combined[0]
+ # Same behavior as above
+ with tf.control_dependencies(df + [dx2]):
x1 = y1 - fx2
- grads_combined = tape.gradient(
+ x = x1, x2
+ dx = dx1, dx2
+ grads = df + dg
+
+ return x, dx, grads
+
+ def backward_grads_with_downsample(self, x, y, dy, training=True):
+ """Manually compute backward gradients given input and output grads."""
+ # Splitting this from `backward_grads` for better readability
+ x1, x2 = x
+ y1, _ = y
+ dy1, dy2 = dy
+
+ with tf.GradientTape() as gtape:
+ gtape.watch(y1)
+ gy1 = self.g(y1, training=training)
+ grads_combined = gtape.gradient(
gy1, [y1] + self.g.trainable_variables, output_gradients=dy2)
dg = grads_combined[1:]
- dx1 = dy1 + grads_combined[0]
+ dz1 = dy1 + grads_combined[0]
- grads_combined = tape.gradient(
- fx2, [x2] + self.f.trainable_variables, output_gradients=dx1)
- dx2 = dy2 + grads_combined[0]
- df = grads_combined[1:]
+ # dx1 need one more step to backprop through downsample
+ with tf.GradientTape() as x1tape:
+ x1tape.watch(x1)
+ z1 = ops.downsample(x1, self.filters // 2, self.strides, axis=self.axis)
+ dx1 = x1tape.gradient(z1, x1, output_gradients=dz1)
- del tape
+ with tf.GradientTape() as ftape:
+ ftape.watch(x2)
+ fx2 = self.f(x2, training=training)
+ grads_combined = ftape.gradient(
+ fx2, [x2] + self.f.trainable_variables, output_gradients=dz1)
+ dx2, df = grads_combined[0], grads_combined[1:]
- grads = df + dg
- vars_ = self.f.trainable_variables + self.g.trainable_variables
+ # dx2 need one more step to backprop through downsample
+ with tf.GradientTape() as x2tape:
+ x2tape.watch(x2)
+ z2 = ops.downsample(x2, self.filters // 2, self.strides, axis=self.axis)
+ dx2 += x2tape.gradient(z2, x2, output_gradients=dy2)
- x = tf.concat([x1, x2], axis=self.axis)
- dx = tf.concat([dx1, dx2], axis=self.axis)
+ dx = dx1, dx2
+ grads = df + dg
- return x, dx, grads, vars_
+ return dx, grads
# Ideally, the following should be wrapped in `tf.keras.Sequential`, however
@@ -422,7 +449,7 @@ class InitBlock(tf.keras.Model):
if self.config.init_max_pool:
net = self.max_pool(net)
- return net
+ return tf.split(net, num_or_size_splits=2, axis=self.axis)
class FinalBlock(tf.keras.Model):
@@ -468,7 +495,7 @@ class FinalBlock(tf.keras.Model):
self.config.n_classes, dtype=self.config.dtype)
def call(self, x, training=True):
- net = x
+ net = tf.concat(x, axis=self.axis)
net = self.batch_norm(net, training=training)
net = self.activation(net)
net = self.global_avg_pool(net)
diff --git a/tensorflow/contrib/eager/python/examples/revnet/blocks_test.py b/tensorflow/contrib/eager/python/examples/revnet/blocks_test.py
index d74785c8fe..fda9020ddf 100644
--- a/tensorflow/contrib/eager/python/examples/revnet/blocks_test.py
+++ b/tensorflow/contrib/eager/python/examples/revnet/blocks_test.py
@@ -116,70 +116,13 @@ def _validate_block_call_channels_first(block_factory, test):
class RevBlockTest(tf.test.TestCase):
- def test_call_channels_first(self):
- """Test `call` function with `channels_first` data format."""
- if not tf.test.is_gpu_available():
- self.skipTest("GPU not available")
-
- with tf.device("/gpu:0"): # Default NCHW format
- input_shape = (128, 8, 8)
- data_shape = (16,) + input_shape
- x = tf.random_normal(shape=data_shape)
-
- # Stride of 1
- block = blocks.RevBlock(
- n_res=3, filters=128, strides=(1, 1), input_shape=input_shape)
- y_tr, y_ev = block(x, training=True), block(x, training=False)
- self.assertEqual(y_tr.shape, y_ev.shape)
- self.assertEqual(y_ev.shape, (16, 128, 8, 8))
- self.assertNotAllClose(y_tr, y_ev)
-
- # Stride of 2
- block = blocks.RevBlock(
- n_res=3, filters=128, strides=(2, 2), input_shape=input_shape)
- y_tr, y_ev = block(x, training=True), block(x, training=False)
- self.assertEqual(y_tr.shape, y_ev.shape)
- self.assertEqual(y_ev.shape, [16, 128, 4, 4])
- self.assertNotAllClose(y_tr, y_ev)
-
- def test_call_channels_last(self):
- """Test `call` function with `channels_last` data format."""
- with tf.device("/cpu:0"): # NHWC format
- input_shape = (8, 8, 128)
- data_shape = (16,) + input_shape
- x = tf.random_normal(shape=data_shape)
-
- # Stride 1
- block = blocks.RevBlock(
- n_res=3,
- filters=128,
- strides=(1, 1),
- input_shape=input_shape,
- data_format="channels_last")
- y_tr, y_ev = block(x, training=True), block(x, training=False)
- self.assertEqual(y_tr.shape, y_ev.shape)
- self.assertEqual(y_ev.shape, (16, 8, 8, 128))
- self.assertNotAllClose(y_tr, y_ev)
-
- # Stride of 2
- block = blocks.RevBlock(
- n_res=3,
- filters=128,
- strides=(2, 2),
- input_shape=input_shape,
- data_format="channels_last")
- y_tr, y_ev = block(x, training=True), block(x, training=False)
- self.assertEqual(y_tr.shape, y_ev.shape)
- self.assertEqual(y_ev.shape, (16, 4, 4, 128))
- self.assertNotAllClose(y_tr, y_ev)
-
def _check_grad_angle(self, grads, grads_true, atol=1e0):
"""Check the angle between two list of vectors are all close."""
for g1, g2 in zip(grads, grads_true):
degree = compute_degree(g1, g2)
self.assertLessEqual(degree, atol)
- def test_backward_grads_and_vars_channels_first(self):
+ def test_backward_grads_channels_first(self):
"""Test `backward` function with `channels_first` data format."""
if not tf.test.is_gpu_available():
self.skipTest("GPU not available")
@@ -190,6 +133,7 @@ class RevBlockTest(tf.test.TestCase):
data_shape = (16,) + input_shape
x = tf.random_normal(shape=data_shape, dtype=tf.float64)
dy = tf.random_normal(shape=data_shape, dtype=tf.float64)
+ dy1, dy2 = tf.split(dy, num_or_size_splits=2, axis=1)
block = blocks.RevBlock(
n_res=3,
filters=128,
@@ -199,9 +143,14 @@ class RevBlockTest(tf.test.TestCase):
dtype=tf.float64)
with tf.GradientTape() as tape:
tape.watch(x)
- y = block(x, training=True)
+ x1, x2 = tf.split(x, num_or_size_splits=2, axis=1)
+ y1, y2 = block((x1, x2), training=True)
+ y = tf.concat((y1, y2), axis=1)
# Compute grads from reconstruction
- dx, dw, vars_ = block.backward_grads_and_vars(x, y, dy, training=True)
+ (dx1, dx2), dw = block.backward_grads(
+ x=(x1, x2), y=(y1, y2), dy=(dy1, dy2), training=True)
+ dx = tf.concat((dx1, dx2), axis=1)
+ vars_ = block.trainable_variables
# Compute true grads
grads = tape.gradient(y, [x] + vars_, output_gradients=dy)
dx_true, dw_true = grads[0], grads[1:]
@@ -213,6 +162,7 @@ class RevBlockTest(tf.test.TestCase):
# Stride 2
x = tf.random_normal(shape=data_shape, dtype=tf.float64)
dy = tf.random_normal(shape=(16, 128, 4, 4), dtype=tf.float64)
+ dy1, dy2 = tf.split(dy, num_or_size_splits=2, axis=1)
block = blocks.RevBlock(
n_res=3,
filters=128,
@@ -222,9 +172,14 @@ class RevBlockTest(tf.test.TestCase):
dtype=tf.float64)
with tf.GradientTape() as tape:
tape.watch(x)
- y = block(x, training=True)
+ x1, x2 = tf.split(x, num_or_size_splits=2, axis=1)
+ y1, y2 = block((x1, x2), training=True)
+ y = tf.concat((y1, y2), axis=1)
# Compute grads from reconstruction
- dx, dw, vars_ = block.backward_grads_and_vars(x, y, dy, training=True)
+ (dx1, dx2), dw = block.backward_grads(
+ x=(x1, x2), y=(y1, y2), dy=(dy1, dy2), training=True)
+ dx = tf.concat((dx1, dx2), axis=1)
+ vars_ = block.trainable_variables
# Compute true grads
grads = tape.gradient(y, [x] + vars_, output_gradients=dy)
dx_true, dw_true = grads[0], grads[1:]
@@ -236,16 +191,7 @@ class RevBlockTest(tf.test.TestCase):
class _ResidualTest(tf.test.TestCase):
- def test_call(self):
- """Test `call` function.
-
- Varying downsampling and data format options.
- """
-
- _validate_block_call_channels_first(blocks._Residual, self)
- _validate_block_call_channels_last(blocks._Residual, self)
-
- def test_backward_grads_and_vars_channels_first(self):
+ def test_backward_grads_channels_first(self):
"""Test `backward_grads` function with `channels_first` data format."""
if not tf.test.is_gpu_available():
self.skipTest("GPU not available")
@@ -256,6 +202,7 @@ class _ResidualTest(tf.test.TestCase):
# Use double precision for testing
x_true = tf.random_normal(shape=data_shape, dtype=tf.float64)
dy = tf.random_normal(shape=data_shape, dtype=tf.float64)
+ dy1, dy2 = tf.split(dy, num_or_size_splits=2, axis=1)
residual = blocks._Residual(
filters=128,
strides=(1, 1),
@@ -264,16 +211,19 @@ class _ResidualTest(tf.test.TestCase):
dtype=tf.float64)
with tf.GradientTape() as tape:
- x_true = tf.identity(x_true)
tape.watch(x_true)
- y = residual(x_true, training=True)
+ x1_true, x2_true = tf.split(x_true, num_or_size_splits=2, axis=1)
+ y1, y2 = residual((x1_true, x2_true), training=True)
+ y = tf.concat((y1, y2), axis=1)
# Gradients computed due to reversibility
- x, dx, dw, vars_ = residual.backward_grads_and_vars(
- y, dy=dy, training=True)
-
+ (x1, x2), (dx1, dx2), dw = residual.backward_grads(
+ y=(y1, y2), dy=(dy1, dy2), training=True)
+ x = tf.concat((x1, x2), axis=1)
+ dx = tf.concat((dx1, dx2), axis=1)
# True gradients computed by the tape
- grads = tape.gradient(y, [x_true] + vars_, output_gradients=dy)
+ grads = tape.gradient(
+ y, [x_true] + residual.trainable_variables, output_gradients=dy)
dx_true, dw_true = grads[0], grads[1:]
self.assertAllClose(x_true, x)
diff --git a/tensorflow/contrib/eager/python/examples/revnet/config.py b/tensorflow/contrib/eager/python/examples/revnet/config.py
index 821a4878c1..29f1db0e03 100644
--- a/tensorflow/contrib/eager/python/examples/revnet/config.py
+++ b/tensorflow/contrib/eager/python/examples/revnet/config.py
@@ -82,7 +82,8 @@ def get_hparams_cifar_38():
config.num_train_images // config.tpu_batch_size)
config.add_hparam("tpu_epochs",
config.max_train_iter // config.tpu_iters_per_epoch)
-
+ config.add_hparam("tpu_eval_steps",
+ config.num_eval_images // config.tpu_eval_batch_size)
return config
@@ -162,7 +163,8 @@ def get_hparams_imagenet_56():
config.num_train_images // config.tpu_batch_size)
config.add_hparam("tpu_epochs",
config.max_train_iter // config.tpu_iters_per_epoch)
-
+ config.add_hparam("tpu_eval_steps",
+ config.num_eval_images // config.tpu_eval_batch_size)
return config
diff --git a/tensorflow/contrib/eager/python/examples/revnet/imagenet_input.py b/tensorflow/contrib/eager/python/examples/revnet/imagenet_input.py
new file mode 100644
index 0000000000..e81351b1b1
--- /dev/null
+++ b/tensorflow/contrib/eager/python/examples/revnet/imagenet_input.py
@@ -0,0 +1,230 @@
+# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Efficient ImageNet input pipeline using tf.data.Dataset."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import functools
+import os
+
+import tensorflow as tf
+
+from tensorflow.contrib.eager.python.examples.revnet import resnet_preprocessing
+
+
+def image_serving_input_fn():
+ """Serving input fn for raw images."""
+
+ def _preprocess_image(image_bytes):
+ """Preprocess a single raw image."""
+ image = resnet_preprocessing.preprocess_image(
+ image_bytes=image_bytes, is_training=False)
+ return image
+
+ image_bytes_list = tf.placeholder(
+ shape=[None],
+ dtype=tf.string,
+ )
+ images = tf.map_fn(
+ _preprocess_image, image_bytes_list, back_prop=False, dtype=tf.float32)
+ return tf.estimator.export.ServingInputReceiver(
+ images, {'image_bytes': image_bytes_list})
+
+
+class ImageNetInput(object):
+ """Generates ImageNet input_fn for training or evaluation.
+
+ The training data is assumed to be in TFRecord format with keys as specified
+ in the dataset_parser below, sharded across 1024 files, named sequentially:
+ train-00000-of-01024
+ train-00001-of-01024
+ ...
+ train-01023-of-01024
+
+ The validation data is in the same format but sharded in 128 files.
+
+ The format of the data required is created by the script at:
+ https://github.com/tensorflow/tpu/blob/master/tools/datasets/imagenet_to_gcs.py
+
+ Args:
+ is_training: `bool` for whether the input is for training
+ data_dir: `str` for the directory of the training and validation data;
+ if 'null' (the literal string 'null', not None), then construct a null
+ pipeline, consisting of empty images.
+ use_bfloat16: If True, use bfloat16 precision; else use float32.
+ transpose_input: 'bool' for whether to use the double transpose trick
+ num_cores: `int` for the number of TPU cores
+ """
+
+ def __init__(self, is_training,
+ use_bfloat16,
+ data_dir,
+ num_cores=8,
+ num_parallel_calls=64,
+ image_size=224,
+ transpose_input=False,
+ cache=False):
+ self.image_preprocessing_fn = resnet_preprocessing.preprocess_image
+ self.is_training = is_training
+ self.use_bfloat16 = use_bfloat16
+ self.data_dir = data_dir
+ self.num_cores = num_cores
+ self.num_parallel_calls = num_parallel_calls
+ if self.data_dir == 'null' or self.data_dir == '':
+ self.data_dir = None
+ self.transpose_input = transpose_input
+ self.image_size = image_size
+ self.cache = cache
+
+ def set_shapes(self, batch_size, images, labels):
+ """Statically set the batch_size dimension."""
+ if self.transpose_input:
+ images.set_shape(images.get_shape().merge_with(
+ tf.TensorShape([None, None, None, batch_size])))
+ labels.set_shape(labels.get_shape().merge_with(
+ tf.TensorShape([batch_size])))
+ else:
+ images.set_shape(images.get_shape().merge_with(
+ tf.TensorShape([batch_size, None, None, None])))
+ labels.set_shape(labels.get_shape().merge_with(
+ tf.TensorShape([batch_size])))
+
+ return images, labels
+
+ def dataset_parser(self, value):
+ """Parse an ImageNet record from a serialized string Tensor."""
+ keys_to_features = {
+ 'image/encoded': tf.FixedLenFeature((), tf.string, ''),
+ 'image/format': tf.FixedLenFeature((), tf.string, 'jpeg'),
+ 'image/class/label': tf.FixedLenFeature([], tf.int64, -1),
+ 'image/class/text': tf.FixedLenFeature([], tf.string, ''),
+ 'image/object/bbox/xmin': tf.VarLenFeature(dtype=tf.float32),
+ 'image/object/bbox/ymin': tf.VarLenFeature(dtype=tf.float32),
+ 'image/object/bbox/xmax': tf.VarLenFeature(dtype=tf.float32),
+ 'image/object/bbox/ymax': tf.VarLenFeature(dtype=tf.float32),
+ 'image/object/class/label': tf.VarLenFeature(dtype=tf.int64),
+ }
+
+ parsed = tf.parse_single_example(value, keys_to_features)
+ image_bytes = tf.reshape(parsed['image/encoded'], shape=[])
+
+ image = self.image_preprocessing_fn(
+ image_bytes=image_bytes,
+ is_training=self.is_training,
+ image_size=self.image_size,
+ use_bfloat16=self.use_bfloat16)
+
+ # Subtract one so that labels are in [0, 1000).
+ label = tf.cast(
+ tf.reshape(parsed['image/class/label'], shape=[]), dtype=tf.int32) - 1
+
+ return image, label
+
+ def input_fn(self, params):
+ """Input function which provides a single batch for train or eval.
+
+ Args:
+ params: `dict` of parameters passed from the `TPUEstimator`.
+ `params['batch_size']` is always provided and should be used as the
+ effective batch size.
+
+ Returns:
+ A `tf.data.Dataset` object.
+ """
+ if self.data_dir is None:
+ tf.logging.info('Using fake input.')
+ return self.input_fn_null(params)
+
+ # Retrieves the batch size for the current shard. The # of shards is
+ # computed according to the input pipeline deployment. See
+ # tf.contrib.tpu.RunConfig for details.
+ batch_size = params['batch_size']
+
+ # Shuffle the filenames to ensure better randomization.
+ file_pattern = os.path.join(
+ self.data_dir, 'train-*' if self.is_training else 'validation-*')
+ dataset = tf.data.Dataset.list_files(file_pattern, shuffle=self.is_training)
+
+ if self.is_training and not self.cache:
+ dataset = dataset.repeat()
+
+ def fetch_dataset(filename):
+ buffer_size = 8 * 1024 * 1024 # 8 MiB per file
+ dataset = tf.data.TFRecordDataset(filename, buffer_size=buffer_size)
+ return dataset
+
+ # Read the data from disk in parallel
+ dataset = dataset.apply(
+ tf.contrib.data.parallel_interleave(
+ fetch_dataset, cycle_length=self.num_parallel_calls, sloppy=True))
+ if self.cache:
+ dataset = dataset.cache().apply(
+ tf.contrib.data.shuffle_and_repeat(1024 * 16))
+ else:
+ dataset = dataset.shuffle(1024)
+
+ # Use the fused map-and-batch operation.
+ #
+ # For XLA, we must used fixed shapes. Because we repeat the source training
+ # dataset indefinitely, we can use `drop_remainder=True` to get fixed-size
+ # batches without dropping any training examples.
+ #
+ # When evaluating, `drop_remainder=True` prevents accidentally evaluating
+ # the same image twice by dropping the final batch if it is less than a full
+ # batch size. As long as this validation is done with consistent batch size,
+ # exactly the same images will be used.
+ dataset = dataset.apply(
+ tf.contrib.data.map_and_batch(
+ self.dataset_parser, batch_size=batch_size,
+ num_parallel_batches=self.num_cores, drop_remainder=True))
+
+ # Transpose for performance on TPU
+ if self.transpose_input:
+ dataset = dataset.map(
+ lambda images, labels: (tf.transpose(images, [1, 2, 3, 0]), labels),
+ num_parallel_calls=self.num_cores)
+
+ # Assign static batch size dimension
+ dataset = dataset.map(functools.partial(self.set_shapes, batch_size))
+
+ # Prefetch overlaps in-feed with training
+ dataset = dataset.prefetch(tf.contrib.data.AUTOTUNE)
+ return dataset
+
+ def input_fn_null(self, params):
+ """Input function which provides null (black) images."""
+ batch_size = params['batch_size']
+ dataset = tf.data.Dataset.range(1).repeat().map(self._get_null_input)
+ dataset = dataset.prefetch(batch_size)
+
+ dataset = dataset.apply(
+ tf.contrib.data.batch_and_drop_remainder(batch_size))
+ if self.transpose_input:
+ dataset = dataset.map(
+ lambda images, labels: (tf.transpose(images, [1, 2, 3, 0]), labels),
+ num_parallel_calls=8)
+
+ dataset = dataset.map(functools.partial(self.set_shapes, batch_size))
+
+ dataset = dataset.prefetch(32) # Prefetch overlaps in-feed with training
+ tf.logging.info('Input dataset: %s', str(dataset))
+ return dataset
+
+ def _get_null_input(self, _):
+ null_image = tf.zeros([224, 224, 3], tf.bfloat16
+ if self.use_bfloat16 else tf.float32)
+ return (null_image, tf.constant(0, tf.int32))
diff --git a/tensorflow/contrib/eager/python/examples/revnet/main.py b/tensorflow/contrib/eager/python/examples/revnet/main.py
index dcd4e1697f..b702e91f92 100644
--- a/tensorflow/contrib/eager/python/examples/revnet/main.py
+++ b/tensorflow/contrib/eager/python/examples/revnet/main.py
@@ -29,6 +29,11 @@ from tensorflow.contrib.eager.python.examples.revnet import revnet
tfe = tf.contrib.eager
+def apply_gradients(optimizer, grads, vars_, global_step=None):
+ """Functional style apply_grads for `tfe.defun`."""
+ optimizer.apply_gradients(zip(grads, vars_), global_step=global_step)
+
+
def main(_):
"""Eager execution workflow with RevNet trained on CIFAR-10."""
tf.enable_eager_execution()
@@ -48,6 +53,11 @@ def main(_):
if FLAGS.use_defun:
model.call = tfe.defun(model.call)
+ model.compute_gradients = tfe.defun(model.compute_gradients)
+ model.get_moving_stats = tfe.defun(model.get_moving_stats)
+ model.restore_moving_stats = tfe.defun(model.restore_moving_stats)
+ global apply_gradients # pylint:disable=global-variable-undefined
+ apply_gradients = tfe.defun(apply_gradients)
if FLAGS.train_dir:
summary_writer = tf.contrib.summary.create_file_writer(FLAGS.train_dir)
@@ -197,9 +207,13 @@ def get_datasets(data_dir, config):
def train_one_iter(model, inputs, labels, optimizer, global_step=None):
"""Train for one iteration."""
- grads, vars_, logits, loss = model.compute_gradients(
- inputs, labels, training=True)
- optimizer.apply_gradients(zip(grads, vars_), global_step=global_step)
+ logits, saved_hiddens = model(inputs, training=True)
+ values = model.get_moving_stats()
+ grads, loss = model.compute_gradients(saved_hiddens, labels)
+ # Restore moving averages when executing eagerly to avoid updating twice
+ model.restore_moving_stats(values)
+ apply_gradients(
+ optimizer, grads, model.trainable_variables, global_step=global_step)
return logits, loss
diff --git a/tensorflow/contrib/eager/python/examples/revnet/main_estimator.py b/tensorflow/contrib/eager/python/examples/revnet/main_estimator.py
index 4868f1931f..3a17eb30da 100644
--- a/tensorflow/contrib/eager/python/examples/revnet/main_estimator.py
+++ b/tensorflow/contrib/eager/python/examples/revnet/main_estimator.py
@@ -53,10 +53,11 @@ def model_fn(features, labels, mode, params):
global_step, config.lr_decay_steps, config.lr_list)
optimizer = tf.train.MomentumOptimizer(
learning_rate, momentum=config.momentum)
- grads, vars_, logits, loss = model.compute_gradients(
- inputs, labels, training=True)
- train_op = optimizer.apply_gradients(
- zip(grads, vars_), global_step=global_step)
+ logits, saved_hidden = model(inputs, training=True)
+ grads, loss = model.compute_gradients(saved_hidden, labels, training=True)
+ with tf.control_dependencies(model.get_updates_for(inputs)):
+ train_op = optimizer.apply_gradients(
+ zip(grads, model.trainable_variables), global_step=global_step)
return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op)
else:
@@ -130,8 +131,7 @@ def get_input_fn(config, data_dir, split):
return input_fn
-def main(argv):
- FLAGS = argv[0] # pylint:disable=invalid-name,redefined-outer-name
+def main(_):
tf.logging.set_verbosity(tf.logging.INFO)
# RevNet specific configuration
@@ -139,7 +139,7 @@ def main(argv):
# Estimator specific configuration
run_config = tf.estimator.RunConfig(
- model_dir=FLAGS.train_dir, # Directory for storing checkpoints
+ model_dir=FLAGS.model_dir, # Directory for storing checkpoints
tf_random_seed=config.seed,
save_summary_steps=config.log_every,
save_checkpoints_steps=config.log_every,
@@ -153,7 +153,7 @@ def main(argv):
# Construct estimator
revnet_estimator = tf.estimator.Estimator(
model_fn=model_fn,
- model_dir=FLAGS.train_dir,
+ model_dir=FLAGS.model_dir,
config=run_config,
params={"config": config})
@@ -173,14 +173,14 @@ def main(argv):
input_fn = tf.estimator.export.build_raw_serving_input_receiver_fn({
"image": inputs
})
- revnet_estimator.export_savedmodel(FLAGS.train_dir, input_fn)
+ revnet_estimator.export_savedmodel(FLAGS.model_dir, input_fn)
if __name__ == "__main__":
flags.DEFINE_string(
"data_dir", default=None, help="Directory to load tfrecords")
flags.DEFINE_string(
- "train_dir",
+ "model_dir",
default=None,
help="[Optional] Directory to store the training information")
flags.DEFINE_string(
@@ -197,4 +197,4 @@ if __name__ == "__main__":
help="[Optional] Architecture of network. "
"Other options include `revnet-110` and `revnet-164`")
FLAGS = flags.FLAGS
- tf.app.run(main=main, argv=[FLAGS])
+ tf.app.run()
diff --git a/tensorflow/contrib/eager/python/examples/revnet/main_estimator_tpu.py b/tensorflow/contrib/eager/python/examples/revnet/main_estimator_tpu.py
index d809bcd287..8520cf5b71 100644
--- a/tensorflow/contrib/eager/python/examples/revnet/main_estimator_tpu.py
+++ b/tensorflow/contrib/eager/python/examples/revnet/main_estimator_tpu.py
@@ -12,22 +12,90 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
-"""Cloud TPU Estimator workflow with RevNet train on CIFAR-10."""
+"""Cloud TPU Estimator workflow with RevNet train on ImageNet."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
-import os
import time
from absl import flags
import tensorflow as tf
-from tensorflow.contrib.eager.python.examples.revnet import cifar_input
-from tensorflow.contrib.eager.python.examples.revnet import main as main_
+from tensorflow.contrib import summary
+from tensorflow.contrib.eager.python.examples.revnet import config as config_
+from tensorflow.contrib.eager.python.examples.revnet import imagenet_input
from tensorflow.contrib.eager.python.examples.revnet import revnet
from tensorflow.contrib.training.python.training import evaluation
-from tensorflow.python.estimator import estimator as estimator_
+from tensorflow.python.estimator import estimator
+
+MEAN_RGB = [0.485, 0.456, 0.406]
+STDDEV_RGB = [0.229, 0.224, 0.225]
+
+
+def _host_call_fn(gs, loss, lr):
+ """Training host call.
+
+ Creates scalar summaries for training metrics.
+
+ This function is executed on the CPU and should not directly reference
+ any Tensors in the rest of the `model_fn`. To pass Tensors from the
+ model to the `metric_fn`, provide as part of the `host_call`. See
+ https://www.tensorflow.org/api_docs/python/tf/contrib/tpu/TPUEstimatorSpec
+ for more information.
+
+ Arguments should match the list of `Tensor` objects passed as the second
+ element in the tuple passed to `host_call`.
+
+ Args:
+ gs: `Tensor with shape `[batch]` for the global_step
+ loss: `Tensor` with shape `[batch]` for the training loss.
+ lr: `Tensor` with shape `[batch]` for the learning_rate.
+
+ Returns:
+ List of summary ops to run on the CPU host.
+ """
+ # Host call fns are executed FLAGS.iterations_per_loop times after one
+ # TPU loop is finished, setting max_queue value to the same as number of
+ # iterations will make the summary writer only flush the data to storage
+ # once per loop.
+ gs = gs[0]
+ with summary.create_file_writer(
+ FLAGS.model_dir, max_queue=FLAGS.iterations_per_loop).as_default():
+ with summary.always_record_summaries():
+ summary.scalar("loss", loss[0], step=gs)
+ summary.scalar("learning_rate", lr[0], step=gs)
+ return summary.all_summary_ops()
+
+
+def _metric_fn(labels, logits):
+ """Evaluation metric function. Evaluates accuracy.
+
+ This function is executed on the CPU and should not directly reference
+ any Tensors in the rest of the `model_fn`. To pass Tensors from the model
+ to the `metric_fn`, provide as part of the `eval_metrics`. See
+ https://www.tensorflow.org/api_docs/python/tf/contrib/tpu/TPUEstimatorSpec
+ for more information.
+
+ Arguments should match the list of `Tensor` objects passed as the second
+ element in the tuple passed to `eval_metrics`.
+
+ Args:
+ labels: `Tensor` with shape `[batch]`.
+ logits: `Tensor` with shape `[batch, num_classes]`.
+
+ Returns:
+ A dict of the metrics to return from evaluation.
+ """
+ predictions = tf.argmax(logits, axis=1)
+ top_1_accuracy = tf.metrics.accuracy(labels, predictions)
+ in_top_5 = tf.cast(tf.nn.in_top_k(logits, labels, 5), tf.float32)
+ top_5_accuracy = tf.metrics.mean(in_top_5)
+
+ return {
+ "top_1_accuracy": top_1_accuracy,
+ "top_5_accuracy": top_5_accuracy,
+ }
def model_fn(features, labels, mode, params):
@@ -42,50 +110,58 @@ def model_fn(features, labels, mode, params):
Returns:
An instance of `tf.contrib.tpu.TPUEstimatorSpec`
"""
+ revnet_config = params["revnet_config"]
+ model = revnet.RevNet(config=revnet_config)
inputs = features
if isinstance(inputs, dict):
inputs = features["image"]
- FLAGS = params["FLAGS"] # pylint:disable=invalid-name,redefined-outer-name
- config = params["config"]
- model = revnet.RevNet(config=config)
+ if revnet_config.data_format == "channels_first":
+ assert not FLAGS.transpose_input # channels_first only for GPU
+ inputs = tf.transpose(inputs, [0, 3, 1, 2])
+
+ if FLAGS.transpose_input and mode != tf.estimator.ModeKeys.PREDICT:
+ inputs = tf.transpose(inputs, [3, 0, 1, 2]) # HWCN to NHWC
+
+ # Normalize the image to zero mean and unit variance.
+ inputs -= tf.constant(MEAN_RGB, shape=[1, 1, 3], dtype=inputs.dtype)
+ inputs /= tf.constant(STDDEV_RGB, shape=[1, 1, 3], dtype=inputs.dtype)
if mode == tf.estimator.ModeKeys.TRAIN:
global_step = tf.train.get_or_create_global_step()
learning_rate = tf.train.piecewise_constant(
- global_step, config.lr_decay_steps, config.lr_list)
- optimizer = tf.train.MomentumOptimizer(
- learning_rate, momentum=config.momentum)
-
+ global_step, revnet_config.lr_decay_steps, revnet_config.lr_list)
+ optimizer = tf.train.MomentumOptimizer(learning_rate,
+ revnet_config.momentum)
if FLAGS.use_tpu:
optimizer = tf.contrib.tpu.CrossShardOptimizer(optimizer)
- # Define gradients
- grads, vars_, logits, loss = model.compute_gradients(
- inputs, labels, training=True)
- train_op = optimizer.apply_gradients(
- zip(grads, vars_), global_step=global_step)
-
- names = [v.name for v in model.variables]
- tf.logging.warn("{}".format(names))
+ logits, saved_hidden = model(inputs, training=True)
+ grads, loss = model.compute_gradients(saved_hidden, labels, training=True)
+ with tf.control_dependencies(model.get_updates_for(inputs)):
+ train_op = optimizer.apply_gradients(
+ zip(grads, model.trainable_variables), global_step=global_step)
+ if not FLAGS.skip_host_call:
+ # To log the loss, current learning rate, and epoch for Tensorboard, the
+ # summary op needs to be run on the host CPU via host_call. host_call
+ # expects [batch_size, ...] Tensors, thus reshape to introduce a batch
+ # dimension. These Tensors are implicitly concatenated to
+ # [params['batch_size']].
+ gs_t = tf.reshape(global_step, [1])
+ loss_t = tf.reshape(loss, [1])
+ lr_t = tf.reshape(learning_rate, [1])
+ host_call = (_host_call_fn, [gs_t, loss_t, lr_t])
return tf.contrib.tpu.TPUEstimatorSpec(
- mode=tf.estimator.ModeKeys.TRAIN, loss=loss, train_op=train_op)
+ mode=mode, loss=loss, train_op=train_op, host_call=host_call)
elif mode == tf.estimator.ModeKeys.EVAL:
logits, _ = model(inputs, training=False)
loss = model.compute_loss(labels=labels, logits=logits)
- def metric_fn(labels, logits):
- predictions = tf.argmax(logits, axis=1)
- accuracy = tf.metrics.accuracy(labels=labels, predictions=predictions)
- return {
- "accuracy": accuracy,
- }
-
return tf.contrib.tpu.TPUEstimatorSpec(
- mode=mode, loss=loss, eval_metrics=(metric_fn, [labels, logits]))
+ mode=mode, loss=loss, eval_metrics=(_metric_fn, [labels, logits]))
else: # Predict or export
logits, _ = model(inputs, training=False)
@@ -102,117 +178,75 @@ def model_fn(features, labels, mode, params):
})
-def get_input_fn(config, data_dir, split):
- """Get the input function required by the `tf.contrib.tpu.TPUEstimator` API.
-
- Args:
- config: Customized hyperparameters
- data_dir: Directory where the data is stored
- split: One of `train`, `validation`, `train_all`, and `test`
-
- Returns:
- Input function required by the `tf.contrib.tpu.TPUEstimator` API
- """
-
- data_dir = os.path.join(data_dir, config.dataset)
- # Fix split-dependent hyperparameters
- if split == "train_all" or split == "train":
- data_aug = True
- epochs = config.tpu_epochs
- shuffle = True
- else:
- data_aug = False
- epochs = 1
- shuffle = False
-
- def input_fn(params):
- """Input function required by the `tf.contrib.tpu.TPUEstimator` API."""
- batch_size = params["batch_size"]
- return cifar_input.get_ds_from_tfrecords(
- data_dir=data_dir,
- split=split,
- data_aug=data_aug,
- batch_size=batch_size, # per-shard batch size
- epochs=epochs,
- shuffle=shuffle,
- prefetch=batch_size, # per-shard batch size
- data_format=config.data_format)
-
- return input_fn
-
-
-def main(argv):
- FLAGS = argv[0] # pylint:disable=invalid-name,redefined-outer-name
+def main(_):
tf.logging.set_verbosity(tf.logging.INFO)
# RevNet specific configuration
- config = main_.get_config(config_name=FLAGS.config, dataset=FLAGS.dataset)
+ revnet_config = {
+ "revnet-56": config_.get_hparams_imagenet_56(),
+ "revnet-104": config_.get_hparams_imagenet_104()
+ }[FLAGS.revnet_config]
if FLAGS.use_tpu:
- tf.logging.info("Using TPU.")
- tpu_cluster_resolver = tf.contrib.cluster_resolver.TPUClusterResolver(
- FLAGS.tpu, zone=FLAGS.tpu_zone, project=FLAGS.gcp_project)
- else:
- tpu_cluster_resolver = None
-
- # TPU specific configuration
- tpu_config = tf.contrib.tpu.TPUConfig(
- # Recommended to be set as number of global steps for next checkpoint
- iterations_per_loop=FLAGS.iterations_per_loop,
- num_shards=FLAGS.num_shards)
+ revnet_config.data_format = "channels_last"
+
+ tpu_cluster_resolver = tf.contrib.cluster_resolver.TPUClusterResolver(
+ FLAGS.tpu, zone=FLAGS.tpu_zone, project=FLAGS.gcp_project)
# Estimator specific configuration
- run_config = tf.contrib.tpu.RunConfig(
+ config = tf.contrib.tpu.RunConfig(
cluster=tpu_cluster_resolver,
model_dir=FLAGS.model_dir,
session_config=tf.ConfigProto(
- allow_soft_placement=True, log_device_placement=False),
- tpu_config=tpu_config,
+ allow_soft_placement=True, log_device_placement=True),
+ tpu_config=tf.contrib.tpu.TPUConfig(
+ iterations_per_loop=FLAGS.iterations_per_loop,
+ num_shards=FLAGS.num_shards,
+ per_host_input_for_training=tf.contrib.tpu.InputPipelineConfig.
+ PER_HOST_V2),
)
- # Construct TPU Estimator
- estimator = tf.contrib.tpu.TPUEstimator(
+ # Input pipelines are slightly different (with regards to shuffling and
+ # preprocessing) between training and evaluation.
+ imagenet_train, imagenet_eval = [
+ imagenet_input.ImageNetInput(
+ is_training=is_training,
+ data_dir=FLAGS.data_dir,
+ transpose_input=FLAGS.transpose_input,
+ use_bfloat16=False) for is_training in [True, False]
+ ]
+
+ revnet_classifier = tf.contrib.tpu.TPUEstimator(
model_fn=model_fn,
use_tpu=FLAGS.use_tpu,
- train_batch_size=config.tpu_batch_size,
- eval_batch_size=config.tpu_eval_batch_size,
- config=run_config,
- params={
- "FLAGS": FLAGS,
- "config": config,
- })
-
- # Construct input functions
- train_input_fn = get_input_fn(
- config=config, data_dir=FLAGS.data_dir, split="train_all")
- eval_input_fn = get_input_fn(
- config=config, data_dir=FLAGS.data_dir, split="test")
-
- # Disabling a range within an else block currently doesn't work
- # due to https://github.com/PyCQA/pylint/issues/872
+ train_batch_size=revnet_config.tpu_batch_size,
+ eval_batch_size=revnet_config.tpu_eval_batch_size,
+ config=config,
+ export_to_tpu=False,
+ params={"revnet_config": revnet_config})
+
+ steps_per_epoch = revnet_config.tpu_iters_per_epoch
+ eval_steps = revnet_config.tpu_eval_steps
+
# pylint: disable=protected-access
if FLAGS.mode == "eval":
- # TPUEstimator.evaluate *requires* a steps argument.
- # Note that the number of examples used during evaluation is
- # --eval_steps * --batch_size.
- # So if you change --batch_size then change --eval_steps too.
- eval_steps = 10000 // config.tpu_eval_batch_size
-
# Run evaluation when there's a new checkpoint
for ckpt in evaluation.checkpoints_iterator(
FLAGS.model_dir, timeout=FLAGS.eval_timeout):
tf.logging.info("Starting to evaluate.")
try:
start_timestamp = time.time() # This time will include compilation time
- eval_results = estimator.evaluate(
- input_fn=eval_input_fn, steps=eval_steps, checkpoint_path=ckpt)
+ eval_results = revnet_classifier.evaluate(
+ input_fn=imagenet_eval.input_fn,
+ steps=eval_steps,
+ checkpoint_path=ckpt)
elapsed_time = int(time.time() - start_timestamp)
tf.logging.info("Eval results: %s. Elapsed seconds: %d" %
(eval_results, elapsed_time))
# Terminate eval job when final checkpoint is reached
current_step = int(os.path.basename(ckpt).split("-")[1])
- if current_step >= config.max_train_iter:
+ if current_step >= revnet_config.max_train_iter:
tf.logging.info(
"Evaluation finished after training step %d" % current_step)
break
@@ -226,37 +260,56 @@ def main(argv):
"Checkpoint %s no longer exists, skipping checkpoint" % ckpt)
else: # FLAGS.mode == 'train' or FLAGS.mode == 'train_and_eval'
- current_step = estimator_._load_global_step_from_checkpoint_dir(
+ current_step = estimator._load_global_step_from_checkpoint_dir(
FLAGS.model_dir)
- tf.logging.info("Training for %d steps . Current"
- " step %d." % (config.max_train_iter, current_step))
+
+ tf.logging.info(
+ "Training for %d steps (%.2f epochs in total). Current"
+ " step %d." % (revnet_config.max_train_iter,
+ revnet_config.max_train_iter / steps_per_epoch,
+ current_step))
start_timestamp = time.time() # This time will include compilation time
+
if FLAGS.mode == "train":
- estimator.train(input_fn=train_input_fn, max_steps=config.max_train_iter)
+ revnet_classifier.train(
+ input_fn=imagenet_train.input_fn,
+ max_steps=revnet_config.max_train_iter)
+
else:
- eval_steps = 10000 // config.tpu_eval_batch_size
assert FLAGS.mode == "train_and_eval"
- while current_step < config.max_train_iter:
+ while current_step < revnet_config.max_train_iter:
# Train for up to steps_per_eval number of steps.
# At the end of training, a checkpoint will be written to --model_dir.
next_checkpoint = min(current_step + FLAGS.steps_per_eval,
- config.max_train_iter)
- estimator.train(input_fn=train_input_fn, max_steps=next_checkpoint)
+ revnet_config.max_train_iter)
+ revnet_classifier.train(
+ input_fn=imagenet_train.input_fn, max_steps=next_checkpoint)
current_step = next_checkpoint
+ tf.logging.info("Finished training up to step %d. Elapsed seconds %d." %
+ (next_checkpoint, int(time.time() - start_timestamp)))
+
# Evaluate the model on the most recent model in --model_dir.
# Since evaluation happens in batches of --eval_batch_size, some images
- # may be consistently excluded modulo the batch size.
+ # may be excluded modulo the batch size. As long as the batch size is
+ # consistent, the evaluated images are also consistent.
tf.logging.info("Starting to evaluate.")
- eval_results = estimator.evaluate(
- input_fn=eval_input_fn, steps=eval_steps)
+ eval_results = revnet_classifier.evaluate(
+ input_fn=imagenet_eval.input_fn, steps=eval_steps)
tf.logging.info("Eval results: %s" % eval_results)
- elapsed_time = int(time.time() - start_timestamp)
- tf.logging.info("Finished training up to step %d. Elapsed seconds %d." %
- (config.max_train_iter, elapsed_time))
- # pylint: enable=protected-access
+ elapsed_time = int(time.time() - start_timestamp)
+ tf.logging.info("Finished training up to step %d. Elapsed seconds %d." %
+ (revnet_config.max_train_iter, elapsed_time))
+
+ if FLAGS.export_dir is not None:
+ # The guide to serve an exported TensorFlow model is at:
+ # https://www.tensorflow.org/serving/serving_basic
+ tf.logging.info("Starting to export model.")
+ revnet_classifier.export_savedmodel(
+ export_dir_base=FLAGS.export_dir,
+ serving_input_receiver_fn=imagenet_input.image_serving_input_fn)
if __name__ == "__main__":
@@ -288,14 +341,10 @@ if __name__ == "__main__":
default=None,
help="[Optional] Directory to store the model information")
flags.DEFINE_string(
- "dataset",
- default="cifar-10",
- help="[Optional] The dataset used; either `cifar-10` or `cifar-100`")
- flags.DEFINE_string(
- "config",
- default="revnet-38",
+ "revnet_config",
+ default="revnet-56",
help="[Optional] Architecture of network. "
- "Other options include `revnet-110` and `revnet-164`")
+ "Other options include `revnet-104`")
flags.DEFINE_boolean(
"use_tpu", default=True, help="[Optional] Whether to use TPU")
flags.DEFINE_integer(
@@ -309,20 +358,37 @@ if __name__ == "__main__":
" train steps, the loop will exit before reaching"
" --iterations_per_loop. The larger this value is, the higher the"
" utilization on the TPU."))
- flags.DEFINE_string(
- "mode",
- default="train_and_eval",
- help="[Optional] Mode to run: train, eval, train_and_eval")
flags.DEFINE_integer(
- "eval_timeout", 60 * 60 * 24,
- "Maximum seconds between checkpoints before evaluation terminates.")
+ "eval_timeout",
+ default=None,
+ help="Maximum seconds between checkpoints before evaluation terminates.")
flags.DEFINE_integer(
"steps_per_eval",
- default=1000,
+ default=5000,
help=(
"Controls how often evaluation is performed. Since evaluation is"
" fairly expensive, it is advised to evaluate as infrequently as"
" possible (i.e. up to --train_steps, which evaluates the model only"
" after finishing the entire training regime)."))
+ flags.DEFINE_bool(
+ "transpose_input",
+ default=True,
+ help="Use TPU double transpose optimization")
+ flags.DEFINE_string(
+ "export_dir",
+ default=None,
+ help=("The directory where the exported SavedModel will be stored."))
+ flags.DEFINE_bool(
+ "skip_host_call",
+ default=False,
+ help=("Skip the host_call which is executed every training step. This is"
+ " generally used for generating training summaries (train loss,"
+ " learning rate, etc...). When --skip_host_call=false, there could"
+ " be a performance drop if host_call function is slow and cannot"
+ " keep up with the TPU-side computation."))
+ flags.DEFINE_string(
+ "mode",
+ default="train_and_eval",
+ help='One of {"train_and_eval", "train", "eval"}.')
FLAGS = flags.FLAGS
- tf.app.run(main=main, argv=[FLAGS])
+ tf.app.run()
diff --git a/tensorflow/contrib/eager/python/examples/revnet/resnet_preprocessing.py b/tensorflow/contrib/eager/python/examples/revnet/resnet_preprocessing.py
new file mode 100644
index 0000000000..21a1ab85d4
--- /dev/null
+++ b/tensorflow/contrib/eager/python/examples/revnet/resnet_preprocessing.py
@@ -0,0 +1,190 @@
+# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""ImageNet preprocessing for ResNet."""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import tensorflow as tf
+
+IMAGE_SIZE = 224
+CROP_PADDING = 32
+
+
+def distorted_bounding_box_crop(image_bytes,
+ bbox,
+ min_object_covered=0.1,
+ aspect_ratio_range=(0.75, 1.33),
+ area_range=(0.05, 1.0),
+ max_attempts=100,
+ scope=None):
+ """Generates cropped_image using one of the bboxes randomly distorted.
+
+ See `tf.image.sample_distorted_bounding_box` for more documentation.
+
+ Args:
+ image_bytes: `Tensor` of binary image data.
+ bbox: `Tensor` of bounding boxes arranged `[1, num_boxes, coords]`
+ where each coordinate is [0, 1) and the coordinates are arranged
+ as `[ymin, xmin, ymax, xmax]`. If num_boxes is 0 then use the whole
+ image.
+ min_object_covered: An optional `float`. Defaults to `0.1`. The cropped
+ area of the image must contain at least this fraction of any bounding
+ box supplied.
+ aspect_ratio_range: An optional list of `float`s. The cropped area of the
+ image must have an aspect ratio = width / height within this range.
+ area_range: An optional list of `float`s. The cropped area of the image
+ must contain a fraction of the supplied image within in this range.
+ max_attempts: An optional `int`. Number of attempts at generating a cropped
+ region of the image of the specified constraints. After `max_attempts`
+ failures, return the entire image.
+ scope: Optional `str` for name scope.
+ Returns:
+ cropped image `Tensor`
+ """
+ with tf.name_scope(scope, 'distorted_bounding_box_crop', [image_bytes, bbox]):
+ shape = tf.image.extract_jpeg_shape(image_bytes)
+ sample_distorted_bounding_box = tf.image.sample_distorted_bounding_box(
+ shape,
+ bounding_boxes=bbox,
+ min_object_covered=min_object_covered,
+ aspect_ratio_range=aspect_ratio_range,
+ area_range=area_range,
+ max_attempts=max_attempts,
+ use_image_if_no_bounding_boxes=True)
+ bbox_begin, bbox_size, _ = sample_distorted_bounding_box
+
+ # Crop the image to the specified bounding box.
+ offset_y, offset_x, _ = tf.unstack(bbox_begin)
+ target_height, target_width, _ = tf.unstack(bbox_size)
+ crop_window = tf.stack([offset_y, offset_x, target_height, target_width])
+ image = tf.image.decode_and_crop_jpeg(image_bytes, crop_window, channels=3)
+
+ return image
+
+
+def _at_least_x_are_equal(a, b, x):
+ """At least `x` of `a` and `b` `Tensors` are equal."""
+ match = tf.equal(a, b)
+ match = tf.cast(match, tf.int32)
+ return tf.greater_equal(tf.reduce_sum(match), x)
+
+
+def _decode_and_random_crop(image_bytes, image_size):
+ """Make a random crop of image_size."""
+ bbox = tf.constant([0.0, 0.0, 1.0, 1.0], dtype=tf.float32, shape=[1, 1, 4])
+ image = distorted_bounding_box_crop(
+ image_bytes,
+ bbox,
+ min_object_covered=0.1,
+ aspect_ratio_range=(3. / 4, 4. / 3.),
+ area_range=(0.08, 1.0),
+ max_attempts=10,
+ scope=None)
+ original_shape = tf.image.extract_jpeg_shape(image_bytes)
+ bad = _at_least_x_are_equal(original_shape, tf.shape(image), 3)
+
+ image = tf.cond(
+ bad,
+ lambda: _decode_and_center_crop(image_bytes, image_size),
+ lambda: tf.image.resize_bicubic([image], # pylint: disable=g-long-lambda
+ [image_size, image_size])[0])
+
+ return image
+
+
+def _decode_and_center_crop(image_bytes, image_size):
+ """Crops to center of image with padding then scales image_size."""
+ shape = tf.image.extract_jpeg_shape(image_bytes)
+ image_height = shape[0]
+ image_width = shape[1]
+
+ padded_center_crop_size = tf.cast(
+ ((image_size / (image_size + CROP_PADDING)) *
+ tf.cast(tf.minimum(image_height, image_width), tf.float32)),
+ tf.int32)
+
+ offset_height = ((image_height - padded_center_crop_size) + 1) // 2
+ offset_width = ((image_width - padded_center_crop_size) + 1) // 2
+ crop_window = tf.stack([offset_height, offset_width,
+ padded_center_crop_size, padded_center_crop_size])
+ image = tf.image.decode_and_crop_jpeg(image_bytes, crop_window, channels=3)
+ image = tf.image.resize_bicubic([image], [image_size, image_size])[0]
+
+ return image
+
+
+def _flip(image):
+ """Random horizontal image flip."""
+ image = tf.image.random_flip_left_right(image)
+ return image
+
+
+def preprocess_for_train(image_bytes, use_bfloat16, image_size=IMAGE_SIZE):
+ """Preprocesses the given image for evaluation.
+
+ Args:
+ image_bytes: `Tensor` representing an image binary of arbitrary size.
+ use_bfloat16: `bool` for whether to use bfloat16.
+ image_size: image size.
+
+ Returns:
+ A preprocessed image `Tensor`.
+ """
+ image = _decode_and_random_crop(image_bytes, image_size)
+ image = _flip(image)
+ image = tf.reshape(image, [image_size, image_size, 3])
+ image = tf.image.convert_image_dtype(
+ image, dtype=tf.bfloat16 if use_bfloat16 else tf.float32)
+ return image
+
+
+def preprocess_for_eval(image_bytes, use_bfloat16, image_size=IMAGE_SIZE):
+ """Preprocesses the given image for evaluation.
+
+ Args:
+ image_bytes: `Tensor` representing an image binary of arbitrary size.
+ use_bfloat16: `bool` for whether to use bfloat16.
+ image_size: image size.
+
+ Returns:
+ A preprocessed image `Tensor`.
+ """
+ image = _decode_and_center_crop(image_bytes, image_size)
+ image = tf.reshape(image, [image_size, image_size, 3])
+ image = tf.image.convert_image_dtype(
+ image, dtype=tf.bfloat16 if use_bfloat16 else tf.float32)
+ return image
+
+
+def preprocess_image(image_bytes,
+ is_training=False,
+ use_bfloat16=False,
+ image_size=IMAGE_SIZE):
+ """Preprocesses the given image.
+
+ Args:
+ image_bytes: `Tensor` representing an image binary of arbitrary size.
+ is_training: `bool` for whether the preprocessing is for training.
+ use_bfloat16: `bool` for whether to use bfloat16.
+ image_size: image size.
+
+ Returns:
+ A preprocessed image `Tensor`.
+ """
+ if is_training:
+ return preprocess_for_train(image_bytes, use_bfloat16, image_size)
+ else:
+ return preprocess_for_eval(image_bytes, use_bfloat16, image_size)
diff --git a/tensorflow/contrib/eager/python/examples/revnet/revnet.py b/tensorflow/contrib/eager/python/examples/revnet/revnet.py
index b1cb312b74..1f2cb14972 100644
--- a/tensorflow/contrib/eager/python/examples/revnet/revnet.py
+++ b/tensorflow/contrib/eager/python/examples/revnet/revnet.py
@@ -24,7 +24,6 @@ from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
-import six
import tensorflow as tf
from tensorflow.contrib.eager.python.examples.revnet import blocks
@@ -45,6 +44,7 @@ class RevNet(tf.keras.Model):
self._init_block = blocks.InitBlock(config=self.config)
self._final_block = blocks.FinalBlock(config=self.config)
self._block_list = self._construct_intermediate_blocks()
+ self._moving_average_variables = []
def _construct_intermediate_blocks(self):
# Precompute input shape after initial block
@@ -128,126 +128,90 @@ class RevNet(tf.keras.Model):
return tf.reduce_mean(cross_ent)
- def compute_gradients(self, inputs, labels, training=True, l2_reg=True):
+ def compute_gradients(self, saved_hidden, labels, training=True, l2_reg=True):
"""Manually computes gradients.
- When eager execution is enabled, this method also SILENTLY updates the
- running averages of batch normalization when `training` is set to True.
+ This method silently updates the running averages of batch normalization.
Args:
- inputs: Image tensor, either NHWC or NCHW, conforming to `data_format`
+ saved_hidden: List of hidden states Tensors
labels: One-hot labels for classification
training: Use the mini-batch stats in batch norm if set to True
l2_reg: Apply l2 regularization
Returns:
- A tuple with the first entry being a list of all gradients, the second
- entry being a list of respective variables, the third being the logits,
- and the forth being the loss
+ A tuple with the first entry being a list of all gradients and the second
+ being the loss
"""
- # Run forward pass to record hidden states
- vars_and_vals = self.get_moving_stats()
- _, saved_hidden = self(inputs, training=training) # pylint:disable=not-callable
- if tf.executing_eagerly():
- # Restore moving averages when executing eagerly to avoid updating twice
- self.restore_moving_stats(vars_and_vals)
- else:
- # Fetch batch norm updates in graph mode
- updates = self.get_updates_for(inputs)
-
- grads_all = []
- vars_all = []
+ def _defunable_pop(l):
+ """Functional style list pop that works with `tfe.defun`."""
+ t, l = l[-1], l[:-1]
+ return t, l
- # Manually backprop through last block
+ # Backprop through last block
x = saved_hidden[-1]
with tf.GradientTape() as tape:
tape.watch(x)
- # Running stats updated here
logits = self._final_block(x, training=training)
loss = self.compute_loss(logits, labels)
-
grads_combined = tape.gradient(loss,
[x] + self._final_block.trainable_variables)
- dy, grads_ = grads_combined[0], grads_combined[1:]
- grads_all += grads_
- vars_all += self._final_block.trainable_variables
+ dy, final_grads = grads_combined[0], grads_combined[1:]
- # Manually backprop through intermediate blocks
+ # Backprop through intermediate blocks
+ intermediate_grads = []
for block in reversed(self._block_list):
- y = saved_hidden.pop()
+ y, saved_hidden = _defunable_pop(saved_hidden)
x = saved_hidden[-1]
- # Running stats updated here
- dy, grads, vars_ = block.backward_grads_and_vars(
- x, y, dy, training=training)
- grads_all += grads
- vars_all += vars_
-
- # Manually backprop through first block
- saved_hidden.pop()
- x = saved_hidden.pop()
- assert not saved_hidden # Cleared after backprop
+ dy, grads = block.backward_grads(x, y, dy, training=training)
+ intermediate_grads = grads + intermediate_grads
+ # Backprop through first block
+ _, saved_hidden = _defunable_pop(saved_hidden)
+ x, saved_hidden = _defunable_pop(saved_hidden)
+ assert not saved_hidden
with tf.GradientTape() as tape:
- # Running stats updated here
y = self._init_block(x, training=training)
-
- grads_all += tape.gradient(
+ init_grads = tape.gradient(
y, self._init_block.trainable_variables, output_gradients=dy)
- vars_all += self._init_block.trainable_variables
- # Apply weight decay
+ # Ordering match up with `model.trainable_variables`
+ grads_all = init_grads + final_grads + intermediate_grads
if l2_reg:
- grads_all = self._apply_weight_decay(grads_all, vars_all)
-
- if not tf.executing_eagerly():
- # Force updates to be executed before gradient computation in graph mode
- # This does nothing when the function is wrapped in defun
- with tf.control_dependencies(updates):
- grads_all[0] = tf.identity(grads_all[0])
+ grads_all = self._apply_weight_decay(grads_all)
- return grads_all, vars_all, logits, loss
+ return grads_all, loss
- def _apply_weight_decay(self, grads, vars_):
+ def _apply_weight_decay(self, grads):
"""Update gradients to reflect weight decay."""
- # Don't decay bias
return [
g + self.config.weight_decay * v if v.name.endswith("kernel:0") else g
- for g, v in zip(grads, vars_)
+ for g, v in zip(grads, self.trainable_variables)
]
def get_moving_stats(self):
- """Get moving averages of batch normalization.
-
- This is needed to avoid updating the running average twice in one iteration.
-
- Returns:
- A dictionary mapping variables for batch normalization moving averages
- to their current values.
- """
- vars_and_vals = {}
-
- def _is_moving_var(v):
- n = v.name
- return n.endswith("moving_mean:0") or n.endswith("moving_variance:0")
+ """Get moving averages of batch normalization."""
+ device = "/gpu:0" if tf.test.is_gpu_available() else "/cpu:0"
+ with tf.device(device):
+ return [v.read_value() for v in self.moving_average_variables]
+ def restore_moving_stats(self, values):
+ """Restore moving averages of batch normalization."""
device = "/gpu:0" if tf.test.is_gpu_available() else "/cpu:0"
with tf.device(device):
- for v in filter(_is_moving_var, self.variables):
- vars_and_vals[v] = v.read_value()
+ for var_, val in zip(self.moving_average_variables, values):
+ var_.assign(val)
- return vars_and_vals
+ @property
+ def moving_average_variables(self):
+ """Get all variables that are batch norm moving averages."""
- def restore_moving_stats(self, vars_and_vals):
- """Restore moving averages of batch normalization.
+ def _is_moving_avg(v):
+ n = v.name
+ return n.endswith("moving_mean:0") or n.endswith("moving_variance:0")
- This is needed to avoid updating the running average twice in one iteration.
+ if not self._moving_average_variables:
+ self._moving_average_variables = filter(_is_moving_avg, self.variables)
- Args:
- vars_and_vals: The dictionary mapping variables to their previous values.
- """
- device = "/gpu:0" if tf.test.is_gpu_available() else "/cpu:0"
- with tf.device(device):
- for var_, val in six.iteritems(vars_and_vals):
- # `assign` causes a copy to GPU (if variable is already on GPU)
- var_.assign(val)
+ return self._moving_average_variables
diff --git a/tensorflow/contrib/eager/python/examples/revnet/revnet_test.py b/tensorflow/contrib/eager/python/examples/revnet/revnet_test.py
index 26b0847523..84b2ddf0de 100644
--- a/tensorflow/contrib/eager/python/examples/revnet/revnet_test.py
+++ b/tensorflow/contrib/eager/python/examples/revnet/revnet_test.py
@@ -31,9 +31,11 @@ tfe = tf.contrib.eager
def train_one_iter(model, inputs, labels, optimizer, global_step=None):
"""Train for one iteration."""
- grads, vars_, logits, loss = model.compute_gradients(
- inputs, labels, training=True)
- optimizer.apply_gradients(zip(grads, vars_), global_step=global_step)
+ logits, saved_hidden = model(inputs)
+ grads, loss = model.compute_gradients(
+ saved_hidden=saved_hidden, labels=labels)
+ optimizer.apply_gradients(
+ zip(grads, model.trainable_variables), global_step=global_step)
return logits, loss
@@ -96,9 +98,10 @@ class RevNetTest(tf.test.TestCase):
def test_compute_gradients(self):
"""Test `compute_gradients` function."""
- self.model(self.x, training=False) # Initialize model
- grads, vars_, logits, loss = self.model.compute_gradients(
- inputs=self.x, labels=self.t, training=True, l2_reg=True)
+ _, saved_hidden = self.model(self.x) # Initialize model
+ grads, loss = self.model.compute_gradients(
+ saved_hidden=saved_hidden, labels=self.t)
+ vars_ = self.model.trainable_variables
self.assertTrue(isinstance(grads, list))
self.assertTrue(isinstance(vars_, list))
self.assertEqual(len(grads), len(vars_))
@@ -107,7 +110,7 @@ class RevNetTest(tf.test.TestCase):
# Compare against the true gradient computed by the tape
with tf.GradientTape() as tape:
- logits, _ = self.model(self.x, training=True)
+ logits, _ = self.model(self.x)
loss_true = self.model.compute_loss(logits=logits, labels=self.t)
grads_true = tape.gradient(loss_true, vars_)
self.assertAllClose(loss, loss_true)
@@ -122,7 +125,9 @@ class RevNetTest(tf.test.TestCase):
def test_compute_gradients_defun(self):
"""Test `compute_gradients` function with defun."""
compute_gradients = tfe.defun(self.model.compute_gradients)
- grads, vars_, _, _ = compute_gradients(self.x, self.t, training=True)
+ _, saved_hidden = self.model(self.x)
+ grads, _ = compute_gradients(saved_hidden=saved_hidden, labels=self.t)
+ vars_ = self.model.trainable_variables
self.assertTrue(isinstance(grads, list))
self.assertTrue(isinstance(vars_, list))
self.assertEqual(len(grads), len(vars_))
@@ -146,10 +151,11 @@ class RevNetTest(tf.test.TestCase):
dtype=tf.int32)
global_step = tf.Variable(0., trainable=False)
model = revnet.RevNet(config=config)
- grads_all, vars_all, _, _ = model.compute_gradients(x, t, training=True)
+ _, saved_hidden = model(x)
+ grads, _ = model.compute_gradients(saved_hidden=saved_hidden, labels=t)
optimizer = tf.train.AdamOptimizer(learning_rate=1e-3)
train_op = optimizer.apply_gradients(
- zip(grads_all, vars_all), global_step=global_step)
+ zip(grads, model.trainable_variables), global_step=global_step)
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
diff --git a/tensorflow/contrib/eager/python/tfe.py b/tensorflow/contrib/eager/python/tfe.py
index ca6430253b..2f0ab616e4 100644
--- a/tensorflow/contrib/eager/python/tfe.py
+++ b/tensorflow/contrib/eager/python/tfe.py
@@ -34,6 +34,7 @@ To use, at program startup, call `tfe.enable_eager_execution()`.
@@run
@@enable_eager_execution
+@@enable_remote_eager_execution
@@custom_gradient
@@ -114,6 +115,7 @@ from tensorflow.python.eager.execution_callbacks import inf_nan_callback
from tensorflow.python.eager.execution_callbacks import nan_callback
from tensorflow.python.eager.execution_callbacks import seterr
from tensorflow.python.framework.ops import enable_eager_execution
+from tensorflow.python.framework.ops import enable_eager_execution_internal as enable_remote_eager_execution
from tensorflow.python.framework.ops import eager_run as run
from tensorflow.python.framework.test_util import run_in_graph_and_eager_modes as run_test_in_graph_and_eager_modes
from tensorflow.python.framework.test_util import run_all_in_graph_and_eager_modes as run_all_tests_in_graph_and_eager_modes
diff --git a/tensorflow/contrib/estimator/BUILD b/tensorflow/contrib/estimator/BUILD
index 1aa3df8d8d..349f48f7f7 100644
--- a/tensorflow/contrib/estimator/BUILD
+++ b/tensorflow/contrib/estimator/BUILD
@@ -28,6 +28,7 @@ py_library(
":multi_head",
":replicate_model_fn",
":rnn",
+ ":saved_model_estimator",
"//tensorflow:tensorflow_py_no_contrib",
],
)
@@ -465,3 +466,43 @@ py_test(
"@absl_py//absl/testing:parameterized",
],
)
+
+py_library(
+ name = "saved_model_estimator",
+ srcs = ["python/estimator/saved_model_estimator.py"],
+ deps = [
+ ":export",
+ "//tensorflow/python:framework_ops",
+ "//tensorflow/python:platform",
+ "//tensorflow/python:training",
+ "//tensorflow/python/estimator",
+ "//tensorflow/python/estimator:export",
+ "//tensorflow/python/estimator:model_fn",
+ "//tensorflow/python/saved_model",
+ ],
+)
+
+py_test(
+ name = "saved_model_estimator_test",
+ size = "medium",
+ srcs = ["python/estimator/saved_model_estimator_test.py"],
+ srcs_version = "PY2AND3",
+ deps = [
+ ":export",
+ ":saved_model_estimator",
+ "//tensorflow/python:array_ops",
+ "//tensorflow/python:client_testlib",
+ "//tensorflow/python:control_flow_ops",
+ "//tensorflow/python:framework_ops",
+ "//tensorflow/python:metrics",
+ "//tensorflow/python:platform",
+ "//tensorflow/python:state_ops",
+ "//tensorflow/python:training",
+ "//tensorflow/python:variables",
+ "//tensorflow/python/data/ops:dataset_ops",
+ "//tensorflow/python/estimator",
+ "//tensorflow/python/estimator:export_export",
+ "//tensorflow/python/estimator:export_output",
+ "//tensorflow/python/estimator:model_fn",
+ ],
+)
diff --git a/tensorflow/contrib/estimator/__init__.py b/tensorflow/contrib/estimator/__init__.py
index 09fcfd66a1..e1453ae1d0 100644
--- a/tensorflow/contrib/estimator/__init__.py
+++ b/tensorflow/contrib/estimator/__init__.py
@@ -33,6 +33,8 @@ from tensorflow.contrib.estimator.python.estimator.logit_fns import *
from tensorflow.contrib.estimator.python.estimator.multi_head import *
from tensorflow.contrib.estimator.python.estimator.replicate_model_fn import *
from tensorflow.contrib.estimator.python.estimator.rnn import *
+from tensorflow.contrib.estimator.python.estimator.saved_model_estimator import *
+from tensorflow.python.estimator.export.export import *
from tensorflow.python.util.all_util import remove_undocumented
# pylint: enable=unused-import,line-too-long,wildcard-import
@@ -70,6 +72,9 @@ _allowed_symbols = [
'stop_if_higher_hook',
'stop_if_no_increase_hook',
'stop_if_no_decrease_hook',
+ 'build_raw_supervised_input_receiver_fn',
+ 'build_supervised_input_receiver_fn_from_input_fn',
+ 'SavedModelEstimator'
]
remove_undocumented(__name__, allowed_exception_list=_allowed_symbols)
diff --git a/tensorflow/contrib/estimator/python/estimator/boosted_trees.py b/tensorflow/contrib/estimator/python/estimator/boosted_trees.py
index 43bfcffd79..7ed77bcce6 100644
--- a/tensorflow/contrib/estimator/python/estimator/boosted_trees.py
+++ b/tensorflow/contrib/estimator/python/estimator/boosted_trees.py
@@ -50,7 +50,8 @@ class _BoostedTreesEstimator(estimator.Estimator):
tree_complexity=0.,
min_node_weight=0.,
config=None,
- center_bias=False):
+ center_bias=False,
+ pruning_mode='none'):
"""Initializes a `BoostedTreesEstimator` instance.
Args:
@@ -89,13 +90,18 @@ class _BoostedTreesEstimator(estimator.Estimator):
regression problems, the first node will return the mean of the labels.
For binary classification problems, it will return a logit for a prior
probability of label 1.
+ pruning_mode: one of 'none', 'pre', 'post' to indicate no pruning, pre-
+ pruning (do not split a node if not enough gain is observed) and post
+ pruning (build the tree up to a max depth and then prune branches with
+ negative gain). For pre and post pruning, you MUST provide
+ tree_complexity >0.
"""
# pylint:disable=protected-access
# HParams for the model.
tree_hparams = canned_boosted_trees._TreeHParams(
n_trees, max_depth, learning_rate, l1_regularization, l2_regularization,
- tree_complexity, min_node_weight, center_bias)
+ tree_complexity, min_node_weight, center_bias, pruning_mode)
def _model_fn(features, labels, mode, config):
return canned_boosted_trees._bt_model_fn(
@@ -129,7 +135,8 @@ def boosted_trees_classifier_train_in_memory(
min_node_weight=0.,
config=None,
train_hooks=None,
- center_bias=False):
+ center_bias=False,
+ pruning_mode='none'):
"""Trains a boosted tree classifier with in memory dataset.
Example:
@@ -208,6 +215,11 @@ def boosted_trees_classifier_train_in_memory(
regression problems, the first node will return the mean of the labels.
For binary classification problems, it will return a logit for a prior
probability of label 1.
+ pruning_mode: one of 'none', 'pre', 'post' to indicate no pruning, pre-
+ pruning (do not split a node if not enough gain is observed) and post
+ pruning (build the tree up to a max depth and then prune branches with
+ negative gain). For pre and post pruning, you MUST provide
+ tree_complexity >0.
Returns:
a `BoostedTreesClassifier` instance created with the given arguments and
@@ -228,7 +240,7 @@ def boosted_trees_classifier_train_in_memory(
# HParams for the model.
tree_hparams = canned_boosted_trees._TreeHParams(
n_trees, max_depth, learning_rate, l1_regularization, l2_regularization,
- tree_complexity, min_node_weight, center_bias)
+ tree_complexity, min_node_weight, center_bias, pruning_mode)
def _model_fn(features, labels, mode, config):
return canned_boosted_trees._bt_model_fn(
@@ -269,7 +281,8 @@ def boosted_trees_regressor_train_in_memory(
min_node_weight=0.,
config=None,
train_hooks=None,
- center_bias=False):
+ center_bias=False,
+ pruning_mode='none'):
"""Trains a boosted tree regressor with in memory dataset.
Example:
@@ -341,6 +354,11 @@ def boosted_trees_regressor_train_in_memory(
regression problems, the first node will return the mean of the labels.
For binary classification problems, it will return a logit for a prior
probability of label 1.
+ pruning_mode: one of 'none', 'pre', 'post' to indicate no pruning, pre-
+ pruning (do not split a node if not enough gain is observed) and post
+ pruning (build the tree up to a max depth and then prune branches with
+ negative gain). For pre and post pruning, you MUST provide
+ tree_complexity >0.
Returns:
a `BoostedTreesClassifier` instance created with the given arguments and
@@ -360,7 +378,7 @@ def boosted_trees_regressor_train_in_memory(
# HParams for the model.
tree_hparams = canned_boosted_trees._TreeHParams(
n_trees, max_depth, learning_rate, l1_regularization, l2_regularization,
- tree_complexity, min_node_weight, center_bias)
+ tree_complexity, min_node_weight, center_bias, pruning_mode)
def _model_fn(features, labels, mode, config):
return canned_boosted_trees._bt_model_fn(
diff --git a/tensorflow/contrib/estimator/python/estimator/boosted_trees_test.py b/tensorflow/contrib/estimator/python/estimator/boosted_trees_test.py
index 999c2aa5e2..b1581f3750 100644
--- a/tensorflow/contrib/estimator/python/estimator/boosted_trees_test.py
+++ b/tensorflow/contrib/estimator/python/estimator/boosted_trees_test.py
@@ -136,6 +136,49 @@ class BoostedTreesEstimatorTest(test_util.TensorFlowTestCase):
eval_res = est.evaluate(input_fn=input_fn, steps=1)
self.assertAllClose(eval_res['average_loss'], 0.614642)
+ def testTrainAndEvaluateEstimatorWithPrePruning(self):
+ input_fn = _make_train_input_fn(is_classification=False)
+
+ est = boosted_trees._BoostedTreesEstimator(
+ feature_columns=self._feature_columns,
+ n_batches_per_layer=1,
+ n_trees=2,
+ head=self._head,
+ max_depth=5,
+ tree_complexity=0.001,
+ pruning_mode='pre')
+
+ num_steps = 100
+ # Train for a few steps, and validate final checkpoint.
+ est.train(input_fn, steps=num_steps)
+ # We stop actually after 2*depth*n_trees steps (via a hook) because we still
+ # could not grow 2 trees of depth 5 (due to pre-pruning).
+ self._assert_checkpoint(
+ est.model_dir, global_step=21, finalized_trees=0, attempted_layers=21)
+ eval_res = est.evaluate(input_fn=input_fn, steps=1)
+ self.assertAllClose(eval_res['average_loss'], 3.83943)
+
+ def testTrainAndEvaluateEstimatorWithPostPruning(self):
+ input_fn = _make_train_input_fn(is_classification=False)
+
+ est = boosted_trees._BoostedTreesEstimator(
+ feature_columns=self._feature_columns,
+ n_batches_per_layer=1,
+ n_trees=2,
+ head=self._head,
+ max_depth=5,
+ tree_complexity=0.001,
+ pruning_mode='post')
+
+ # It will stop after 10 steps because of the max depth and num trees.
+ num_steps = 100
+ # Train for a few steps, and validate final checkpoint.
+ est.train(input_fn, steps=num_steps)
+ self._assert_checkpoint(
+ est.model_dir, global_step=10, finalized_trees=2, attempted_layers=10)
+ eval_res = est.evaluate(input_fn=input_fn, steps=1)
+ self.assertAllClose(eval_res['average_loss'], 2.37652)
+
def testInferEstimator(self):
train_input_fn = _make_train_input_fn(is_classification=False)
predict_input_fn = numpy_io.numpy_input_fn(
@@ -231,6 +274,31 @@ class BoostedTreesEstimatorTest(test_util.TensorFlowTestCase):
self.assertAllClose([[0], [1], [1], [0], [0]],
[pred['class_ids'] for pred in predictions])
+ def testBinaryClassifierTrainInMemoryAndEvalAndInferWithPrePruning(self):
+ train_input_fn = _make_train_input_fn(is_classification=True)
+ predict_input_fn = numpy_io.numpy_input_fn(
+ x=FEATURES_DICT, y=None, batch_size=1, num_epochs=1, shuffle=False)
+
+ est = boosted_trees.boosted_trees_classifier_train_in_memory(
+ train_input_fn=train_input_fn,
+ feature_columns=self._feature_columns,
+ n_trees=1,
+ max_depth=5,
+ pruning_mode='pre',
+ tree_complexity=0.01)
+ # We stop actually after 2*depth*n_trees steps (via a hook) because we still
+ # could not grow 1 trees of depth 5 (due to pre-pruning).
+ self._assert_checkpoint(
+ est.model_dir, global_step=11, finalized_trees=0, attempted_layers=11)
+
+ # Check evaluate and predict.
+ eval_res = est.evaluate(input_fn=train_input_fn, steps=1)
+ self.assertAllClose(eval_res['accuracy'], 1.0)
+ # Validate predictions.
+ predictions = list(est.predict(input_fn=predict_input_fn))
+ self.assertAllClose([[0], [1], [1], [0], [0]],
+ [pred['class_ids'] for pred in predictions])
+
def testBinaryClassifierTrainInMemoryWithDataset(self):
train_input_fn = _make_train_input_fn_dataset(is_classification=True)
predict_input_fn = numpy_io.numpy_input_fn(
diff --git a/tensorflow/contrib/estimator/python/estimator/saved_model_estimator.py b/tensorflow/contrib/estimator/python/estimator/saved_model_estimator.py
new file mode 100644
index 0000000000..ce98e9987e
--- /dev/null
+++ b/tensorflow/contrib/estimator/python/estimator/saved_model_estimator.py
@@ -0,0 +1,449 @@
+# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Class that creates an Estimator from a SavedModel."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import six
+
+from tensorflow.python.estimator import estimator as estimator_lib
+from tensorflow.python.estimator import model_fn as model_fn_lib
+from tensorflow.python.estimator.export import export as export_lib
+from tensorflow.python.estimator.export import export_output
+from tensorflow.python.framework import dtypes
+from tensorflow.python.framework import ops
+from tensorflow.python.framework import tensor_shape
+from tensorflow.python.platform import tf_logging as logging
+from tensorflow.python.saved_model import constants
+from tensorflow.python.saved_model import loader_impl
+from tensorflow.python.saved_model import signature_constants
+from tensorflow.python.training import checkpoint_utils
+from tensorflow.python.training import monitored_session
+from tensorflow.python.training import training_util
+
+
+class SavedModelEstimator(estimator_lib.Estimator):
+ """Create an Estimator from a SavedModel.
+
+ Only SavedModels exported with
+ `tf.contrib.estimator.export_all_saved_models()` or
+ `tf.estimator.Estimator.export_savedmodel()` are supported for this class.
+
+ Example with `tf.estimator.DNNClassifier`:
+
+ **Step 1: Create and train DNNClassifier.**
+
+ ```python
+ feature1 = tf.feature_column.embedding_column(
+ tf.feature_column.categorical_column_with_vocabulary_list(
+ key='feature1', vocabulary_list=('green', 'yellow')), dimension=1)
+ feature2 = tf.feature_column.numeric_column(key='feature2', default_value=0.0)
+
+ classifier = tf.estimator.DNNClassifier(
+ hidden_units=[4,2], feature_columns=[feature1, feature2])
+
+ def input_fn():
+ features = {'feature1': tf.constant(['green', 'green', 'yellow']),
+ 'feature2': tf.constant([3.5, 4.2, 6.1])}
+ label = tf.constant([1., 0., 0.])
+ return tf.data.Dataset.from_tensors((features, label)).repeat()
+
+ classifier.train(input_fn=input_fn, steps=10)
+ ```
+
+ **Step 2: Export classifier.**
+ First, build functions that specify the expected inputs.
+
+ ```python
+ # During train and evaluation, both the features and labels should be defined.
+ supervised_input_receiver_fn = (
+ tf.contrib.estimator.build_raw_supervised_input_receiver_fn(
+ {'feature1': tf.placeholder(dtype=tf.string, shape=[None]),
+ 'feature2': tf.placeholder(dtype=tf.float32, shape=[None])},
+ tf.placeholder(dtype=tf.float32, shape=[None])))
+
+ # During predict mode, expect to receive a `tf.Example` proto, so a parsing
+ # function is used.
+ serving_input_receiver_fn = (
+ tf.estimator.export.build_parsing_serving_input_receiver_fn(
+ tf.feature_column.make_parse_example_spec([feature1, feature2])))
+ ```
+
+ Next, export the model as a SavedModel. A timestamped directory will be
+ created (for example `/tmp/export_all/1234567890`).
+
+ ```python
+ # Option 1: Save all modes (train, eval, predict)
+ export_dir = tf.contrib.estimator.export_all_saved_models(
+ classifier, '/tmp/export_all',
+ {tf.estimator.ModeKeys.TRAIN: supervised_input_receiver_fn,
+ tf.estimator.ModeKeys.EVAL: supervised_input_receiver_fn,
+ tf.estimator.ModeKeys.PREDICT: serving_input_receiver_fn})
+
+ # Option 2: Only export predict mode
+ export_dir = classifier.export_savedmodel(
+ '/tmp/export_predict', serving_input_receiver_fn)
+ ```
+
+ **Step 3: Create a SavedModelEstimator from the exported SavedModel.**
+
+ ```python
+ est = tf.contrib.estimator.SavedModelEstimator(export_dir)
+
+ # If all modes were exported, you can immediately evaluate and predict, or
+ # continue training. Otherwise only predict is available.
+ eval_results = est.evaluate(input_fn=input_fn, steps=1)
+ print(eval_results)
+
+ est.train(input_fn=input_fn, steps=20)
+
+ def predict_input_fn():
+ example = tf.train.Example()
+ example.features.feature['feature1'].bytes_list.value.extend(['yellow'])
+ example.features.feature['feature2'].float_list.value.extend([1.])
+ return {'inputs':tf.constant([example.SerializeToString()])}
+
+ predictions = est.predict(predict_input_fn)
+ print(next(predictions))
+ ```
+ """
+
+ def __init__(self, saved_model_dir, model_dir=None):
+ """Initialize a SavedModelEstimator.
+
+ The SavedModelEstimator loads its model function and variable values from
+ the graphs defined in the SavedModel. There is no option to pass in
+ `RunConfig` or `params` arguments, because the model function graph is
+ defined statically in the SavedModel.
+
+ Args:
+ saved_model_dir: Directory containing SavedModel protobuf and subfolders.
+ model_dir: Directory to save new checkpoints during training.
+
+ Raises:
+ NotImplementedError: If a DistributionStrategy is defined in the config.
+ Unless the SavedModelEstimator is subclassed, this shouldn't happen.
+ """
+ checkpoint = estimator_lib._get_saved_model_ckpt(saved_model_dir) # pylint: disable=protected-access
+ vars_to_warm_start = [name for name, _ in
+ checkpoint_utils.list_variables(checkpoint)]
+ warm_start_settings = estimator_lib.WarmStartSettings(
+ ckpt_to_initialize_from=checkpoint,
+ vars_to_warm_start=vars_to_warm_start)
+
+ super(SavedModelEstimator, self).__init__(
+ model_fn=self._model_fn_from_saved_model, model_dir=model_dir,
+ warm_start_from=warm_start_settings)
+ if self._train_distribution or self._eval_distribution:
+ raise NotImplementedError(
+ 'SavedModelEstimator currently does not support '
+ 'DistributionStrategy.')
+ self.saved_model_dir = saved_model_dir
+ self.saved_model_loader = loader_impl.SavedModelLoader(saved_model_dir)
+ self._available_modes = self._extract_available_modes()
+
+ def _extract_available_modes(self):
+ """Return list of modes found in SavedModel."""
+ available_modes = []
+ logging.info('Checking available modes for SavedModelEstimator.')
+ for mode in [model_fn_lib.ModeKeys.TRAIN, model_fn_lib.ModeKeys.EVAL,
+ model_fn_lib.ModeKeys.PREDICT]:
+ try:
+ self._get_meta_graph_def_for_mode(mode)
+ except RuntimeError:
+ logging.warning('%s mode not found in SavedModel.' % mode)
+ continue
+
+ if self._get_signature_def_for_mode(mode) is not None:
+ available_modes.append(mode)
+
+ logging.info('Available modes for Estimator: %s' % available_modes)
+ return available_modes
+
+ def _validate_mode(self, mode):
+ """Make sure that mode can be run using the SavedModel."""
+ if mode not in self._available_modes:
+ raise RuntimeError('%s mode is not available in the SavedModel. Use '
+ 'saved_model_cli to check that the Metagraph for this '
+ 'mode has been exported.' % mode)
+
+ def _get_meta_graph_def_for_mode(self, mode):
+ tags = model_fn_lib.EXPORT_TAG_MAP[mode]
+ return self.saved_model_loader.get_meta_graph_def_from_tags(tags)
+
+ def _get_signature_def_for_mode(self, mode):
+ meta_graph_def = self._get_meta_graph_def_for_mode(mode)
+ sig_def_key = (signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY
+ if mode == model_fn_lib.ModeKeys.PREDICT else mode)
+ if sig_def_key not in meta_graph_def.signature_def:
+ logging.warning('Metagraph for mode %s was found, but SignatureDef with'
+ ' key \"%s\" is missing.' % (mode, sig_def_key))
+ return None
+ return meta_graph_def.signature_def[sig_def_key]
+
+ def _create_and_assert_global_step(self, graph):
+ # Do nothing here. The global step variable will be created/loaded from the
+ # SavedModel. If a global step variable were created here, the result
+ # will be two duplicate global step variables, causing issues during
+ # the warm-start phase.
+ # Due to the global variable being created in the model function, this may
+ # cause issues when running DistributionStrategy. Thus, DistributionStrategy
+ # is not yet supported with SavedModelEstimator.
+ return None
+
+ def _model_fn_from_saved_model(self, features, labels, mode):
+ """Load a SavedModel graph and return an EstimatorSpec."""
+ # TODO(kathywu): Model function loads placeholders from the graph. Calling
+ # export_all_saved_models creates another placeholder for the inputs, on top
+ # of the original placeholders. There should be a way to avoid this.
+ self._validate_mode(mode)
+
+ g = ops.get_default_graph()
+ if training_util.get_global_step(g) is not None:
+ raise RuntimeError(
+ 'Graph must not contain a global step tensor before the SavedModel is'
+ ' loaded. Please make sure that the input function does not create a '
+ 'global step.')
+
+ # Extract SignatureDef for information about the input and output tensors.
+ signature_def = self._get_signature_def_for_mode(mode)
+
+ # Generate input map for replacing the inputs in the SavedModel graph with
+ # the provided features and labels.
+ input_map = _generate_input_map(signature_def, features, labels)
+
+ # Create a list of the names of output tensors. When the graph is loaded,
+ # names of the output tensors may be remapped. This ensures that the correct
+ # tensors are returned in the EstimatorSpec.
+ output_tensor_names = [
+ value.name for value in six.itervalues(signature_def.outputs)]
+
+ # Load the graph. `output_tensors` contains output `Tensors` in the same
+ # same order as the `output_tensor_names` list.
+ tags = model_fn_lib.EXPORT_TAG_MAP[mode]
+ _, output_tensors = self.saved_model_loader.load_graph(
+ g, tags, input_map=input_map, return_elements=output_tensor_names)
+
+ # Create a scaffold from the MetaGraphDef that contains ops to initialize
+ # the graph. This should mirror the steps from _add_meta_graph_for_mode(),
+ # which creates a MetaGraphDef from the EstimatorSpec's scaffold.
+ scaffold = monitored_session.Scaffold(
+ local_init_op=loader_impl._get_main_op_tensor( # pylint: disable=protected-access
+ self._get_meta_graph_def_for_mode(mode)))
+
+ # Ensure that a global step tensor has been created.
+ global_step_tensor = training_util.get_global_step(g)
+ training_util.assert_global_step(global_step_tensor)
+
+ # Extract values to return in the EstimatorSpec.
+ output_map = dict(zip(output_tensor_names, output_tensors))
+ outputs = {key: output_map[value.name]
+ for key, value in six.iteritems(signature_def.outputs)}
+
+ loss, predictions, metrics = _validate_and_extract_outputs(
+ mode, outputs, signature_def.method_name)
+
+ train_op = ops.get_collection(constants.TRAIN_OP_KEY)
+ if len(train_op) > 1:
+ raise RuntimeError('Multiple ops found in the train_op collection.')
+ train_op = None if not train_op else train_op[0]
+
+ _clear_saved_model_collections()
+ return model_fn_lib.EstimatorSpec(
+ scaffold=scaffold,
+ mode=mode,
+ loss=loss,
+ train_op=train_op,
+ predictions=predictions,
+ eval_metric_ops=metrics)
+
+
+def _clear_saved_model_collections():
+ """Clear collections that are expected empty when exporting a SavedModel.
+
+ The SavedModel builder uses these collections to track ops necessary to
+ restore the graph state. These collections are expected to be empty before
+ MetaGraphs are added to the builder.
+ """
+ del ops.get_collection_ref(constants.ASSETS_KEY)[:]
+ del ops.get_collection_ref(constants.LEGACY_INIT_OP_KEY)[:]
+ del ops.get_collection_ref(constants.MAIN_OP_KEY)[:]
+ del ops.get_collection_ref(constants.TRAIN_OP_KEY)[:]
+
+
+def _generate_input_map(signature_def, features, labels):
+ """Return dict mapping an input tensor name to a feature or label tensor.
+
+ Args:
+ signature_def: SignatureDef loaded from SavedModel
+ features: A `Tensor`, `SparseTensor`, or dict of string to `Tensor` or
+ `SparseTensor`, specifying the features to be passed to the model.
+ labels: A `Tensor`, `SparseTensor`, or dict of string to `Tensor` or
+ `SparseTensor`, specifying the labels to be passed to the model. May be
+ `None`.
+
+ Returns:
+ dict mapping string names of inputs to features or labels tensors
+
+ Raises:
+ ValueError: if SignatureDef inputs are not completely mapped by the input
+ features and labels.
+ """
+ # pylint: disable=protected-access
+ if not isinstance(features, dict):
+ features = {export_lib._SINGLE_FEATURE_DEFAULT_NAME: features}
+ if labels is not None and not isinstance(labels, dict):
+ labels = {export_lib._SINGLE_LABEL_DEFAULT_NAME: labels}
+ # pylint: enable=protected-access
+
+ inputs = signature_def.inputs
+ input_map = {}
+ for key, tensor_info in six.iteritems(inputs):
+ input_name = tensor_info.name
+ if ':' in input_name:
+ input_name = input_name[:input_name.find(':')]
+
+ # When tensors are used as control inputs for operations, their names are
+ # prepended with a '^' character in the GraphDef. To handle possible control
+ # flow edge cases, control input names must be included in the input map.
+ control_dependency_name = '^' + input_name
+
+ if key in features:
+ _check_same_dtype_and_shape(features[key], tensor_info, key)
+ input_map[input_name] = input_map[control_dependency_name] = features[key]
+ elif labels is not None and key in labels:
+ _check_same_dtype_and_shape(labels[key], tensor_info, key)
+ input_map[input_name] = input_map[control_dependency_name] = labels[key]
+ else:
+ raise ValueError(
+ 'Key \"%s\" not found in features or labels passed in to the model '
+ 'function. All required keys: %s' % (key, inputs.keys()))
+
+ return input_map
+
+
+def _check_same_dtype_and_shape(tensor, tensor_info, name):
+ """Validate that tensor has the same properties as the TensorInfo proto.
+
+ Args:
+ tensor: a `Tensor` object.
+ tensor_info: a `TensorInfo` proto.
+ name: Name of the input (to identify Tensor if an error is raised).
+
+ Raises:
+ ValueError: If the tensor shape or dtype don't match the TensorInfo
+ """
+ dtype_error = (tensor.dtype != dtypes.DType(tensor_info.dtype))
+ shape_error = not tensor.shape.is_compatible_with(tensor_info.tensor_shape)
+
+ if dtype_error or shape_error:
+ msg = 'Tensor shape and/or dtype validation failed for input %s:' % name
+ if dtype_error:
+ msg += ('\n\tExpected dtype: %s, Got: %s'
+ % (dtypes.DType(tensor_info.dtype), tensor.dtype))
+ if shape_error:
+ msg += ('\n\tExpected shape: %s, Got: %s'
+ % (tensor_shape.TensorShape(tensor_info.tensor_shape),
+ tensor.shape))
+
+ raise ValueError(msg)
+
+
+def _extract_eval_metrics(output_dict):
+ """Return a eval metric dict extracted from the output_dict.
+
+ Eval metrics consist of a value tensor and an update op. Both must be in the
+ passed-in tensor dictionary for an eval metric to be added to the returned
+ dictionary.
+
+ Args:
+ output_dict: a dict that maps strings to tensors.
+
+ Returns:
+ dict mapping strings to (value, update_op) tuples.
+ """
+ # pylint: disable=protected-access
+ metric_ops = {}
+ separator_char = export_output._SupervisedOutput._SEPARATOR_CHAR
+
+ for key, tensor in six.iteritems(output_dict):
+ split_key = key.split(separator_char)
+
+ # The metric name may contain the separator character, so recreate its name.
+ metric_name = separator_char.join(split_key[:-1])
+
+ if split_key[0] == export_output._SupervisedOutput.METRICS_NAME:
+ # If the key ends with the value suffix, and there is a corresponding
+ # key ending with the update_op suffix, then add tensors to metrics dict.
+ if split_key[-1] == export_output._SupervisedOutput.METRIC_VALUE_SUFFIX:
+ update_op = ''.join(
+ [metric_name, separator_char,
+ export_output._SupervisedOutput.METRIC_UPDATE_SUFFIX])
+ if update_op in output_dict:
+ update_op_tensor = output_dict[update_op]
+ metric_ops[metric_name] = (tensor, update_op_tensor)
+
+ # pylint: enable=protected-access
+ return metric_ops
+
+
+def _validate_and_extract_outputs(mode, output_dict, method_name):
+ """Extract values from SignatureDef output dictionary.
+
+ Args:
+ mode: One of the modes enumerated in `tf.estimator.ModeKeys`.
+ output_dict: dict of string SignatureDef keys to `Tensor`.
+ method_name: Method name of the SignatureDef as a string.
+
+ Returns:
+ Tuple of (
+ loss: `Tensor` object,
+ predictions: dictionary mapping string keys to `Tensor` objects,
+ metrics: dictionary mapping string keys to a tuple of two `Tensor` objects
+ )
+
+ Raises:
+ RuntimeError: raised if SignatureDef has an invalid method name for the mode
+ """
+ # pylint: disable=protected-access
+ loss, predictions, metrics = None, None, None
+
+ if mode == model_fn_lib.ModeKeys.PREDICT:
+ predictions = output_dict
+ else:
+ # Validate that the SignatureDef's method name matches the expected name for
+ # the given mode.
+ expected_method_name = signature_constants.SUPERVISED_TRAIN_METHOD_NAME
+ if mode == model_fn_lib.ModeKeys.EVAL:
+ expected_method_name = signature_constants.SUPERVISED_EVAL_METHOD_NAME
+ if method_name != expected_method_name:
+ raise RuntimeError(
+ 'Invalid SignatureDef method name for mode %s.\n\tExpected: %s\n\t'
+ 'Got: %s\nPlease ensure that the SavedModel was exported with '
+ '`tf.contrib.estimator.export_all_saved_models()`.' %
+ (mode, expected_method_name, method_name))
+
+ # Extract loss, metrics and predictions from the output dict.
+ loss = output_dict[export_output._SupervisedOutput.LOSS_NAME]
+ metrics = _extract_eval_metrics(output_dict)
+ predictions = {
+ key: value for key, value in six.iteritems(output_dict)
+ if key.split(export_output._SupervisedOutput._SEPARATOR_CHAR)[0] == (
+ export_output._SupervisedOutput.PREDICTIONS_NAME)}
+
+ # pylint: enable=protected-access
+ return loss, predictions, metrics
diff --git a/tensorflow/contrib/estimator/python/estimator/saved_model_estimator_test.py b/tensorflow/contrib/estimator/python/estimator/saved_model_estimator_test.py
new file mode 100644
index 0000000000..718da1367c
--- /dev/null
+++ b/tensorflow/contrib/estimator/python/estimator/saved_model_estimator_test.py
@@ -0,0 +1,369 @@
+# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Tests for SavedModelEstimator."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import shutil
+import tempfile
+
+from tensorflow.contrib.estimator.python.estimator import export as contrib_export
+from tensorflow.contrib.estimator.python.estimator import saved_model_estimator
+from tensorflow.python.data.ops import dataset_ops
+from tensorflow.python.estimator import estimator
+from tensorflow.python.estimator import model_fn as model_fn_lib
+from tensorflow.python.estimator.export import export
+from tensorflow.python.estimator.export import export_output
+from tensorflow.python.framework import constant_op
+from tensorflow.python.framework import dtypes
+from tensorflow.python.framework import ops
+from tensorflow.python.ops import array_ops
+from tensorflow.python.ops import control_flow_ops
+from tensorflow.python.ops import metrics as metrics_lib
+from tensorflow.python.ops import state_ops
+from tensorflow.python.ops import variables
+from tensorflow.python.platform import test
+from tensorflow.python.training import monitored_session
+from tensorflow.python.training import training
+
+
+def dummy_input_fn():
+ return dataset_ops.Dataset.from_tensors((
+ {'x': constant_op.constant([[1], [-2]], dtype=dtypes.int64)},
+ constant_op.constant([[4], [-3]], dtype=dtypes.float32))).repeat()
+
+
+def dummy_input_fn_features_only():
+ return dataset_ops.Dataset.from_tensors(
+ {'x': constant_op.constant([[5], [6]], dtype=dtypes.int64)}).repeat()
+
+
+def dummy_supervised_receiver_fn():
+ feature_spec = {
+ 'x': array_ops.placeholder(
+ dtype=dtypes.int64, shape=(2, 1), name='feature_x'),
+ }
+ label_spec = array_ops.placeholder(
+ dtype=dtypes.float32, shape=[2, 1], name='truth')
+ return export.build_raw_supervised_input_receiver_fn(
+ feature_spec, label_spec)
+
+
+def dummy_serving_receiver_fn():
+ feature_spec = {'x': array_ops.placeholder(
+ dtype=dtypes.int64, shape=(2, 1), name='feature_x'),}
+ return export.build_raw_serving_input_receiver_fn(feature_spec)
+
+
+def model_fn_diff_modes(features, labels, mode):
+ _, _ = features, labels
+ v = variables.Variable(21, name='some_var')
+ train_op = None
+ loss = constant_op.constant(104)
+ if mode == model_fn_lib.ModeKeys.TRAIN:
+ loss = constant_op.constant(105)
+ predictions = constant_op.constant([501])
+ train_op = control_flow_ops.group(
+ state_ops.assign_add(training.get_global_step(), 1),
+ state_ops.assign_add(v, 3))
+ elif mode == model_fn_lib.ModeKeys.EVAL:
+ loss = constant_op.constant(106)
+ predictions = constant_op.constant([502])
+ else:
+ loss = constant_op.constant(107)
+ predictions = constant_op.constant([503])
+ return model_fn_lib.EstimatorSpec(
+ mode,
+ loss=loss,
+ train_op=train_op,
+ eval_metric_ops={
+ 'abs_err': metrics_lib.mean_absolute_error(
+ constant_op.constant(0), predictions)},
+ predictions=predictions)
+
+
+class SavedModelEstimatorTest(test.TestCase):
+
+ def setUp(self):
+ self.tmpdirs = []
+
+ def tearDown(self):
+ for tmpdir in self.tmpdirs:
+ # gfile.DeleteRecursively fails in the windows cmake test, so use shutil.
+ shutil.rmtree(tmpdir, ignore_errors=True)
+ self.tmpdirs = []
+
+ def _get_tmp_dir(self):
+ tmpdir = tempfile.mkdtemp()
+ self.tmpdirs.append(tmpdir)
+ return tmpdir
+
+ def _export_estimator(self, train=True, evaluate=True, predict=True,
+ model_fn=model_fn_diff_modes):
+ est = estimator.Estimator(model_fn, self._get_tmp_dir())
+ est.train(input_fn=dummy_input_fn, steps=10)
+
+ input_receiver_fn_map = {}
+ if train:
+ input_receiver_fn_map[model_fn_lib.ModeKeys.TRAIN] = (
+ dummy_supervised_receiver_fn())
+ if evaluate:
+ input_receiver_fn_map[model_fn_lib.ModeKeys.EVAL] = (
+ dummy_supervised_receiver_fn())
+ if predict:
+ input_receiver_fn_map[model_fn_lib.ModeKeys.PREDICT] = (
+ dummy_serving_receiver_fn())
+
+ export_base_path = self._get_tmp_dir()
+ export_dir = contrib_export.export_all_saved_models(
+ est, export_base_path, input_receiver_fn_map)
+ return export_dir
+
+ def test_load_all_modes(self):
+ sme = saved_model_estimator.SavedModelEstimator(
+ self._export_estimator(), self._get_tmp_dir())
+ sme.train(input_fn=dummy_input_fn, steps=1)
+ sme.train(input_fn=dummy_input_fn, steps=2)
+ self.assertEqual(13, sme.get_variable_value('global_step'))
+ self.assertEqual(60, sme.get_variable_value('some_var'))
+
+ eval_results = sme.evaluate(dummy_input_fn, steps=5)
+
+ self.assertEqual(13, eval_results['global_step'])
+ self.assertEqual(106, eval_results['loss'])
+ self.assertEqual(502, eval_results['metrics/abs_err'])
+
+ predictions = next(sme.predict(dummy_input_fn_features_only))
+ self.assertDictEqual({'output': 503}, predictions)
+
+ def test_load_all_modes_no_train(self):
+ """Ensure that all functions can be used without requiring a ckpt."""
+ sme = saved_model_estimator.SavedModelEstimator(
+ self._export_estimator(), self._get_tmp_dir())
+ eval_results = sme.evaluate(dummy_input_fn, steps=5)
+ self.assertEqual(10, eval_results['global_step'])
+ self.assertEqual(106, eval_results['loss'])
+ self.assertEqual(502, eval_results['metrics/abs_err'])
+
+ predictions = next(sme.predict(dummy_input_fn_features_only))
+ self.assertDictEqual({'output': 503}, predictions)
+
+ def test_partial_exported_estimator(self):
+ sme1 = saved_model_estimator.SavedModelEstimator(
+ self._export_estimator(train=False, predict=False), self._get_tmp_dir())
+ sme1.evaluate(dummy_input_fn, steps=5)
+ with self.assertRaisesRegexp(RuntimeError, 'train mode is not available'):
+ sme1.train(input_fn=dummy_input_fn, steps=1)
+ with self.assertRaisesRegexp(RuntimeError, 'infer mode is not available'):
+ next(sme1.predict(dummy_input_fn_features_only))
+
+ sme2 = saved_model_estimator.SavedModelEstimator(
+ self._export_estimator(evaluate=False), self._get_tmp_dir())
+ sme2.train(input_fn=dummy_input_fn, steps=1)
+ next(sme2.predict(dummy_input_fn_features_only))
+ with self.assertRaisesRegexp(RuntimeError, 'eval mode is not available'):
+ sme2.evaluate(dummy_input_fn, steps=5)
+
+ def test_with_incorrect_input(self):
+ sme = saved_model_estimator.SavedModelEstimator(
+ self._export_estimator(), self._get_tmp_dir())
+
+ def bad_shape_input_fn():
+ return dataset_ops.Dataset.from_tensors((
+ {'x': constant_op.constant([1, 2], dtype=dtypes.int64)},
+ constant_op.constant([1, 2], dtype=dtypes.float32)))
+
+ with self.assertRaisesRegexp(ValueError, 'Expected shape'):
+ sme.train(bad_shape_input_fn, steps=1)
+
+ def bad_dtype_input_fn():
+ return dataset_ops.Dataset.from_tensors((
+ {'x': constant_op.constant([[1], [1]], dtype=dtypes.int32)},
+ constant_op.constant([[1], [1]], dtype=dtypes.int64)))
+
+ with self.assertRaisesRegexp(ValueError, 'Expected dtype'):
+ sme.train(bad_dtype_input_fn, steps=1)
+
+ def test_input_fn_with_global_step(self):
+ sme = saved_model_estimator.SavedModelEstimator(
+ self._export_estimator(), self._get_tmp_dir())
+
+ def bad_input_fn():
+ training.get_or_create_global_step()
+ return dataset_ops.Dataset.from_tensors((
+ {'x': constant_op.constant([[1], [1]], dtype=dtypes.int64)},
+ constant_op.constant([[1], [1]], dtype=dtypes.float32)))
+
+ with self.assertRaisesRegexp(RuntimeError,
+ 'Graph must not contain a global step tensor'):
+ sme.train(bad_input_fn, steps=1)
+
+ def test_re_export_saved_model_serving_only(self):
+ sme = saved_model_estimator.SavedModelEstimator(
+ self._export_estimator(), self._get_tmp_dir())
+ sme.train(dummy_input_fn, steps=3)
+ self.assertEqual(13, sme.get_variable_value('global_step'))
+ self.assertEqual(60, sme.get_variable_value('some_var'))
+
+ predictions = next(sme.predict(dummy_input_fn_features_only))
+ self.assertDictEqual({'output': 503}, predictions)
+
+ # Export SavedModel, and test that the variable and prediction values are
+ # the same.
+ sme_export_dir = sme.export_savedmodel(
+ self._get_tmp_dir(), dummy_serving_receiver_fn())
+
+ sme2 = saved_model_estimator.SavedModelEstimator(
+ sme_export_dir, self._get_tmp_dir())
+ self.assertEqual(60, sme.get_variable_value('some_var'))
+ self.assertEqual(13, sme.get_variable_value('global_step'))
+
+ predictions = next(sme2.predict(dummy_input_fn_features_only))
+ self.assertDictEqual({'output': 503}, predictions)
+
+ def test_re_export_saved_model(self):
+ sme = saved_model_estimator.SavedModelEstimator(
+ self._export_estimator(), self._get_tmp_dir())
+ self.assertDictEqual(
+ {'loss': 106, 'metrics/abs_err': 502, 'global_step': 10},
+ sme.evaluate(dummy_input_fn, steps=1))
+
+ sme.train(dummy_input_fn, steps=3)
+ self.assertDictEqual(
+ {'loss': 106, 'metrics/abs_err': 502, 'global_step': 13},
+ sme.evaluate(dummy_input_fn, steps=1))
+ self.assertEqual(60, sme.get_variable_value('some_var'))
+
+ predictions = next(sme.predict(dummy_input_fn_features_only))
+ self.assertDictEqual({'output': 503}, predictions)
+
+ # Export SavedModel for all modes
+ input_receiver_fn_map = {
+ model_fn_lib.ModeKeys.TRAIN: dummy_supervised_receiver_fn(),
+ model_fn_lib.ModeKeys.EVAL: dummy_supervised_receiver_fn(),
+ model_fn_lib.ModeKeys.PREDICT: dummy_serving_receiver_fn()}
+ sme_export_dir = contrib_export.export_all_saved_models(
+ sme, self._get_tmp_dir(), input_receiver_fn_map)
+
+ sme2 = saved_model_estimator.SavedModelEstimator(
+ sme_export_dir, self._get_tmp_dir())
+ self.assertDictEqual(
+ {'loss': 106, 'metrics/abs_err': 502, 'global_step': 13},
+ sme.evaluate(dummy_input_fn, steps=1))
+ self.assertEqual(60, sme.get_variable_value('some_var'))
+
+ sme.train(dummy_input_fn, steps=7)
+ self.assertEqual(20, sme.get_variable_value('global_step'))
+
+ predictions = next(sme2.predict(dummy_input_fn_features_only))
+ self.assertDictEqual({'output': 503}, predictions)
+
+ def test_load_saved_model_from_serving_only(self):
+ def model_fn(features, labels, mode):
+ _, _ = features, labels
+ return model_fn_lib.EstimatorSpec(
+ mode,
+ loss=constant_op.constant([103]),
+ train_op=state_ops.assign_add(training.get_global_step(), 1),
+ predictions=constant_op.constant([502]),
+ export_outputs={'test': export_output.ClassificationOutput(
+ constant_op.constant([[32.]]))})
+
+ est = estimator.Estimator(model_fn, self._get_tmp_dir())
+ est.train(input_fn=dummy_input_fn, steps=10)
+
+ def serving_input_receiver_fn():
+ return export.ServingInputReceiver(
+ {'test-features': constant_op.constant([[1], [1]])},
+ array_ops.placeholder(dtype=dtypes.string))
+
+ export_dir = est.export_savedmodel(
+ self._get_tmp_dir(), serving_input_receiver_fn)
+
+ sme = saved_model_estimator.SavedModelEstimator(
+ export_dir, self._get_tmp_dir())
+
+ def input_fn():
+ return {'inputs': constant_op.constant('someinputstr')}
+
+ prediction = next(sme.predict(input_fn))
+ self.assertDictEqual({'scores': 32}, prediction)
+
+ def test_with_local_init_op(self):
+ def model_fn(features, labels, mode):
+ _, _ = features, labels
+ v = variables.Variable(21, name='some_var')
+ scaffold = monitored_session.Scaffold(
+ local_init_op=state_ops.assign_add(v, -3).op
+ )
+ return model_fn_lib.EstimatorSpec(
+ mode,
+ scaffold=scaffold,
+ train_op=state_ops.assign_add(training.get_global_step(), 1),
+ loss=array_ops.identity(v))
+ export_dir = self._export_estimator(predict=False, model_fn=model_fn)
+ sme = saved_model_estimator.SavedModelEstimator(
+ export_dir, self._get_tmp_dir())
+
+ eval_results1 = sme.evaluate(dummy_input_fn, steps=2)
+ self.assertEqual(15, eval_results1['loss'])
+
+ sme.train(dummy_input_fn, steps=1)
+ self.assertEqual(15, sme.get_variable_value('some_var'))
+
+ eval_results2 = sme.evaluate(dummy_input_fn, steps=5)
+ self.assertEqual(12, eval_results2['loss'])
+
+ def test_with_working_input_fn(self):
+ def model_fn(features, labels, mode):
+ loss = None
+ if labels is not None:
+ loss = labels[0][0] + labels[1][0]
+ return model_fn_lib.EstimatorSpec(
+ mode,
+ loss=loss,
+ train_op=state_ops.assign_add(training.get_global_step(), 1),
+ predictions={'features_0': array_ops.identity([features['x'][0][0]]),
+ 'features_1': array_ops.identity([features['x'][1][0]])})
+
+ sme = saved_model_estimator.SavedModelEstimator(
+ self._export_estimator(model_fn=model_fn), self._get_tmp_dir())
+ eval_results = sme.evaluate(dummy_input_fn, steps=1)
+ self.assertEqual(1, eval_results['loss'])
+
+ predictions = next(sme.predict(dummy_input_fn_features_only))
+ self.assertDictEqual({'features_0': 5, 'features_1': 6}, predictions)
+
+ def test_control_dependency(self):
+ # Control dependencies are saved with "^" appended to the start of the input
+ # name. The input map must include control dependencies as well.
+ def model_fn(features, labels, mode):
+ _ = labels
+ with ops.control_dependencies([features['x']]):
+ loss = features['x'][1][0]
+ return model_fn_lib.EstimatorSpec(
+ mode,
+ loss=loss,
+ train_op=state_ops.assign_add(training.get_global_step(), 1))
+ sme = saved_model_estimator.SavedModelEstimator(
+ self._export_estimator(train=False, predict=False, model_fn=model_fn),
+ self._get_tmp_dir())
+ sme.evaluate(dummy_input_fn, steps=1) # Should run without error
+
+
+if __name__ == '__main__':
+ test.main()
diff --git a/tensorflow/contrib/gan/BUILD b/tensorflow/contrib/gan/BUILD
index 7e6cb72485..053d4e3e97 100644
--- a/tensorflow/contrib/gan/BUILD
+++ b/tensorflow/contrib/gan/BUILD
@@ -196,11 +196,16 @@ py_test(
srcs = ["python/losses/python/tuple_losses_test.py"],
srcs_version = "PY2AND3",
deps = [
+ ":losses_impl",
":namedtuples",
":tuple_losses",
+ "//tensorflow/contrib/layers:layers_py",
+ "//tensorflow/python:array_ops",
"//tensorflow/python:client_testlib",
"//tensorflow/python:constant_op",
"//tensorflow/python:dtypes",
+ "//tensorflow/python:math_ops",
+ "//tensorflow/python:variable_scope",
"//tensorflow/python:variables",
"//third_party/py/numpy",
],
diff --git a/tensorflow/contrib/gan/python/losses/python/tuple_losses_impl.py b/tensorflow/contrib/gan/python/losses/python/tuple_losses_impl.py
index dcc3f94c2d..221c70c38b 100644
--- a/tensorflow/contrib/gan/python/losses/python/tuple_losses_impl.py
+++ b/tensorflow/contrib/gan/python/losses/python/tuple_losses_impl.py
@@ -80,6 +80,9 @@ __all__ = [
'mutual_information_penalty',
'combine_adversarial_loss',
'cycle_consistency_loss',
+ 'stargan_generator_loss_wrapper',
+ 'stargan_discriminator_loss_wrapper',
+ 'stargan_gradient_penalty_wrapper'
]
@@ -277,3 +280,86 @@ def cycle_consistency_loss(cyclegan_model, scope=None, add_summaries=False):
cyclegan_model.model_x2y.generator_inputs, cyclegan_model.reconstructed_x,
cyclegan_model.model_y2x.generator_inputs, cyclegan_model.reconstructed_y,
scope, add_summaries)
+
+
+def stargan_generator_loss_wrapper(loss_fn):
+ """Convert a generator loss function to take a StarGANModel.
+
+ The new function has the same name as the original one.
+
+ Args:
+ loss_fn: A python function taking Discriminator's real/fake prediction for
+ generated data.
+
+ Returns:
+ A new function that takes a StarGANModel namedtuple and returns the same
+ loss.
+ """
+
+ def new_loss_fn(stargan_model, **kwargs):
+ return loss_fn(
+ stargan_model.discriminator_generated_data_source_predication, **kwargs)
+
+ new_docstring = """The stargan_model version of %s.""" % loss_fn.__name__
+ new_loss_fn.__docstring__ = new_docstring
+ new_loss_fn.__name__ = loss_fn.__name__
+ new_loss_fn.__module__ = loss_fn.__module__
+ return new_loss_fn
+
+
+def stargan_discriminator_loss_wrapper(loss_fn):
+ """Convert a discriminator loss function to take a StarGANModel.
+
+ The new function has the same name as the original one.
+
+ Args:
+ loss_fn: A python function taking Discriminator's real/fake prediction for
+ real data and generated data.
+
+ Returns:
+ A new function that takes a StarGANModel namedtuple and returns the same
+ loss.
+ """
+
+ def new_loss_fn(stargan_model, **kwargs):
+ return loss_fn(
+ stargan_model.discriminator_input_data_source_predication,
+ stargan_model.discriminator_generated_data_source_predication, **kwargs)
+
+ new_docstring = """The stargan_model version of %s.""" % loss_fn.__name__
+ new_loss_fn.__docstring__ = new_docstring
+ new_loss_fn.__name__ = loss_fn.__name__
+ new_loss_fn.__module__ = loss_fn.__module__
+ return new_loss_fn
+
+
+def stargan_gradient_penalty_wrapper(loss_fn):
+ """Convert a gradient penalty function to take a StarGANModel.
+
+ The new function has the same name as the original one.
+
+ Args:
+ loss_fn: A python function taking real_data, generated_data,
+ generator_inputs for Discriminator's condition (i.e. number of domains),
+ discriminator_fn, and discriminator_scope.
+
+ Returns:
+ A new function that takes a StarGANModel namedtuple and returns the same
+ loss.
+ """
+
+ def new_loss_fn(stargan_model, **kwargs):
+ num_domains = stargan_model.input_data_domain_label.shape.as_list()[-1]
+ return loss_fn(
+ real_data=stargan_model.input_data,
+ generated_data=stargan_model.generated_data,
+ generator_inputs=num_domains,
+ discriminator_fn=stargan_model.discriminator_fn,
+ discriminator_scope=stargan_model.discriminator_scope,
+ **kwargs)
+
+ new_docstring = """The stargan_model version of %s.""" % loss_fn.__name__
+ new_loss_fn.__docstring__ = new_docstring
+ new_loss_fn.__name__ = loss_fn.__name__
+ new_loss_fn.__module__ = loss_fn.__module__
+ return new_loss_fn
diff --git a/tensorflow/contrib/gan/python/losses/python/tuple_losses_test.py b/tensorflow/contrib/gan/python/losses/python/tuple_losses_test.py
index aa1ef11172..a559bbfa11 100644
--- a/tensorflow/contrib/gan/python/losses/python/tuple_losses_test.py
+++ b/tensorflow/contrib/gan/python/losses/python/tuple_losses_test.py
@@ -22,10 +22,15 @@ import collections
import numpy as np
+from tensorflow.contrib import layers
from tensorflow.contrib.gan.python import namedtuples
+from tensorflow.contrib.gan.python.losses.python import losses_impl as tfgan_losses_impl
from tensorflow.contrib.gan.python.losses.python import tuple_losses_impl as tfgan_losses
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import dtypes
+from tensorflow.python.ops import array_ops
+from tensorflow.python.ops import math_ops
+from tensorflow.python.ops import variable_scope
from tensorflow.python.ops import variables
from tensorflow.python.platform import test
@@ -129,6 +134,9 @@ manual_tests = [
'mutual_information_penalty',
'wasserstein_gradient_penalty',
'cycle_consistency_loss',
+ 'stargan_generator_loss_wrapper',
+ 'stargan_discriminator_loss_wrapper',
+ 'stargan_gradient_penalty_wrapper'
]
discriminator_keyword_args = {
@@ -175,6 +183,112 @@ class CycleConsistencyLossTest(test.TestCase):
self.assertNear(5.0, loss.eval(), 1e-5)
+class StarGANLossWrapperTest(test.TestCase):
+
+ def setUp(self):
+
+ super(StarGANLossWrapperTest, self).setUp()
+
+ self.input_data = array_ops.ones([1, 2, 2, 3])
+ self.input_data_domain_label = constant_op.constant([[0, 1]])
+ self.generated_data = array_ops.ones([1, 2, 2, 3])
+ self.discriminator_input_data_source_predication = array_ops.ones([1])
+ self.discriminator_generated_data_source_predication = array_ops.ones([1])
+
+ def _discriminator_fn(inputs, num_domains):
+ """Differentiable dummy discriminator for StarGAN."""
+ hidden = layers.flatten(inputs)
+ output_src = math_ops.reduce_mean(hidden, axis=1)
+ output_cls = layers.fully_connected(
+ inputs=hidden,
+ num_outputs=num_domains,
+ activation_fn=None,
+ normalizer_fn=None,
+ biases_initializer=None)
+ return output_src, output_cls
+
+ with variable_scope.variable_scope('discriminator') as dis_scope:
+ pass
+
+ self.model = namedtuples.StarGANModel(
+ input_data=self.input_data,
+ input_data_domain_label=self.input_data_domain_label,
+ generated_data=self.generated_data,
+ generated_data_domain_target=None,
+ reconstructed_data=None,
+ discriminator_input_data_source_predication=self.
+ discriminator_input_data_source_predication,
+ discriminator_generated_data_source_predication=self.
+ discriminator_generated_data_source_predication,
+ discriminator_input_data_domain_predication=None,
+ discriminator_generated_data_domain_predication=None,
+ generator_variables=None,
+ generator_scope=None,
+ generator_fn=None,
+ discriminator_variables=None,
+ discriminator_scope=dis_scope,
+ discriminator_fn=_discriminator_fn)
+
+ self.discriminator_fn = _discriminator_fn
+ self.discriminator_scope = dis_scope
+
+ def test_stargan_generator_loss_wrapper(self):
+ """Test StarGAN generator loss wrapper."""
+ loss_fn = tfgan_losses_impl.wasserstein_generator_loss
+ wrapped_loss_fn = tfgan_losses.stargan_generator_loss_wrapper(loss_fn)
+
+ loss_result_tensor = loss_fn(
+ self.discriminator_generated_data_source_predication)
+ wrapped_loss_result_tensor = wrapped_loss_fn(self.model)
+
+ with self.test_session() as sess:
+ sess.run(variables.global_variables_initializer())
+ loss_result, wrapped_loss_result = sess.run(
+ [loss_result_tensor, wrapped_loss_result_tensor])
+ self.assertAlmostEqual(loss_result, wrapped_loss_result)
+
+ def test_stargan_discriminator_loss_wrapper(self):
+ """Test StarGAN discriminator loss wrapper."""
+ loss_fn = tfgan_losses_impl.wasserstein_discriminator_loss
+ wrapped_loss_fn = tfgan_losses.stargan_discriminator_loss_wrapper(loss_fn)
+
+ loss_result_tensor = loss_fn(
+ self.discriminator_generated_data_source_predication,
+ self.discriminator_generated_data_source_predication)
+ wrapped_loss_result_tensor = wrapped_loss_fn(self.model)
+
+ with self.test_session() as sess:
+ sess.run(variables.global_variables_initializer())
+ loss_result, wrapped_loss_result = sess.run(
+ [loss_result_tensor, wrapped_loss_result_tensor])
+ self.assertAlmostEqual(loss_result, wrapped_loss_result)
+
+ def test_stargan_gradient_penalty_wrapper(self):
+ """Test StaGAN gradient penalty wrapper.
+
+ Notes:
+ The random interpolates are handled by given setting the reconstruction to
+ be the same as the input.
+
+ """
+ loss_fn = tfgan_losses_impl.wasserstein_gradient_penalty
+ wrapped_loss_fn = tfgan_losses.stargan_gradient_penalty_wrapper(loss_fn)
+
+ loss_result_tensor = loss_fn(
+ real_data=self.input_data,
+ generated_data=self.generated_data,
+ generator_inputs=self.input_data_domain_label.shape.as_list()[-1],
+ discriminator_fn=self.discriminator_fn,
+ discriminator_scope=self.discriminator_scope)
+ wrapped_loss_result_tensor = wrapped_loss_fn(self.model)
+
+ with self.test_session() as sess:
+ sess.run(variables.global_variables_initializer())
+ loss_result, wrapped_loss_result = sess.run(
+ [loss_result_tensor, wrapped_loss_result_tensor])
+ self.assertAlmostEqual(loss_result, wrapped_loss_result)
+
+
if __name__ == '__main__':
for loss_name in tfgan_losses.__all__:
if loss_name in manual_tests: continue
diff --git a/tensorflow/contrib/gan/python/train.py b/tensorflow/contrib/gan/python/train.py
index df603d1f18..03f52d214b 100644
--- a/tensorflow/contrib/gan/python/train.py
+++ b/tensorflow/contrib/gan/python/train.py
@@ -34,6 +34,7 @@ from __future__ import print_function
from tensorflow.contrib.framework.python.ops import variables as variables_lib
from tensorflow.contrib.gan.python import losses as tfgan_losses
from tensorflow.contrib.gan.python import namedtuples
+from tensorflow.contrib.gan.python.losses.python import losses_impl as tfgan_losses_impl
from tensorflow.contrib.slim.python.slim import learning as slim_learning
from tensorflow.contrib.training.python.training import training
from tensorflow.python.framework import dtypes
@@ -41,14 +42,17 @@ from tensorflow.python.framework import ops
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import check_ops
from tensorflow.python.ops import init_ops
+from tensorflow.python.ops import math_ops
from tensorflow.python.ops import random_ops
from tensorflow.python.ops import variable_scope
from tensorflow.python.ops.distributions import distribution as ds
from tensorflow.python.ops.losses import losses
+from tensorflow.python.summary import summary
from tensorflow.python.training import session_run_hook
from tensorflow.python.training import sync_replicas_optimizer
from tensorflow.python.training import training_util
+
__all__ = [
'gan_model',
'infogan_model',
@@ -751,6 +755,130 @@ def cyclegan_loss(
return namedtuples.CycleGANLoss(loss_x2y, loss_y2x)
+def stargan_loss(
+ model,
+ generator_loss_fn=tfgan_losses.stargan_generator_loss_wrapper(
+ tfgan_losses_impl.wasserstein_generator_loss),
+ discriminator_loss_fn=tfgan_losses.stargan_discriminator_loss_wrapper(
+ tfgan_losses_impl.wasserstein_discriminator_loss),
+ gradient_penalty_weight=10.0,
+ gradient_penalty_epsilon=1e-10,
+ gradient_penalty_target=1.0,
+ gradient_penalty_one_sided=False,
+ reconstruction_loss_fn=losses.absolute_difference,
+ reconstruction_loss_weight=10.0,
+ classification_loss_fn=losses.softmax_cross_entropy,
+ classification_loss_weight=1.0,
+ classification_one_hot=True,
+ add_summaries=True):
+ """StarGAN Loss.
+
+ The four major part can be found here: http://screen/tMRMBAohDYG.
+
+ Args:
+ model: (StarGAN) Model output of the stargan_model() function call.
+ generator_loss_fn: The loss function on the generator. Takes a
+ `StarGANModel` named tuple.
+ discriminator_loss_fn: The loss function on the discriminator. Takes a
+ `StarGANModel` namedtuple.
+ gradient_penalty_weight: (float) Gradient penalty weight. Default to 10 per
+ the original paper https://arxiv.org/abs/1711.09020. Set to 0 or None to
+ turn off gradient penalty.
+ gradient_penalty_epsilon: (float) A small positive number added for
+ numerical stability when computing the gradient norm.
+ gradient_penalty_target: (float, or tf.float `Tensor`) The target value of
+ gradient norm. Defaults to 1.0.
+ gradient_penalty_one_sided: (bool) If `True`, penalty proposed in
+ https://arxiv.org/abs/1709.08894 is used. Defaults to `False`.
+ reconstruction_loss_fn: The reconstruction loss function. Default to L1-norm
+ and the function must conform to the `tf.losses` API.
+ reconstruction_loss_weight: Reconstruction loss weight. Default to 10.0.
+ classification_loss_fn: The loss function on the discriminator's ability to
+ classify domain of the input. Default to one-hot softmax cross entropy
+ loss, and the function must conform to the `tf.losses` API.
+ classification_loss_weight: (float) Classification loss weight. Default to
+ 1.0.
+ classification_one_hot: (bool) If the label is one hot representation.
+ Default to True. If False, classification classification_loss_fn need to
+ be sigmoid cross entropy loss instead.
+ add_summaries: (bool) Add the loss to the summary
+
+ Returns:
+ GANLoss namedtuple where we have generator loss and discriminator loss.
+
+ Raises:
+ ValueError: If input StarGANModel.input_data_domain_label does not have rank
+ 2, or dimension 2 is not defined.
+ """
+
+ def _classification_loss_helper(true_labels, predict_logits, scope_name):
+ """Classification Loss Function Helper.
+
+ Args:
+ true_labels: Tensor of shape [batch_size, num_domains] representing the
+ label where each row is an one-hot vector.
+ predict_logits: Tensor of shape [batch_size, num_domains] representing the
+ predicted label logit, which is UNSCALED output from the NN.
+ scope_name: (string) Name scope of the loss component.
+
+ Returns:
+ Single scalar tensor representing the classification loss.
+ """
+
+ with ops.name_scope(scope_name, values=(true_labels, predict_logits)):
+
+ loss = classification_loss_fn(
+ onehot_labels=true_labels, logits=predict_logits)
+
+ if not classification_one_hot:
+ loss = math_ops.reduce_sum(loss, axis=1)
+ loss = math_ops.reduce_mean(loss)
+
+ if add_summaries:
+ summary.scalar(scope_name, loss)
+
+ return loss
+
+ # Check input shape.
+ model.input_data_domain_label.shape.assert_has_rank(2)
+ model.input_data_domain_label.shape[1:].assert_is_fully_defined()
+
+ # Adversarial Loss.
+ generator_loss = generator_loss_fn(model, add_summaries=add_summaries)
+ discriminator_loss = discriminator_loss_fn(model, add_summaries=add_summaries)
+
+ # Gradient Penalty.
+ if _use_aux_loss(gradient_penalty_weight):
+ gradient_penalty_fn = tfgan_losses.stargan_gradient_penalty_wrapper(
+ tfgan_losses_impl.wasserstein_gradient_penalty)
+ discriminator_loss += gradient_penalty_fn(
+ model,
+ epsilon=gradient_penalty_epsilon,
+ target=gradient_penalty_target,
+ one_sided=gradient_penalty_one_sided,
+ add_summaries=add_summaries) * gradient_penalty_weight
+
+ # Reconstruction Loss.
+ reconstruction_loss = reconstruction_loss_fn(model.input_data,
+ model.reconstructed_data)
+ generator_loss += reconstruction_loss * reconstruction_loss_weight
+ if add_summaries:
+ summary.scalar('reconstruction_loss', reconstruction_loss)
+
+ # Classification Loss.
+ generator_loss += _classification_loss_helper(
+ true_labels=model.generated_data_domain_target,
+ predict_logits=model.discriminator_generated_data_domain_predication,
+ scope_name='generator_classification_loss') * classification_loss_weight
+ discriminator_loss += _classification_loss_helper(
+ true_labels=model.input_data_domain_label,
+ predict_logits=model.discriminator_input_data_domain_predication,
+ scope_name='discriminator_classification_loss'
+ ) * classification_loss_weight
+
+ return namedtuples.GANLoss(generator_loss, discriminator_loss)
+
+
def _get_update_ops(kwargs, gen_scope, dis_scope, check_for_unused_ops=True):
"""Gets generator and discriminator update ops.
diff --git a/tensorflow/contrib/gan/python/train_test.py b/tensorflow/contrib/gan/python/train_test.py
index fa52e9cca1..58f348034f 100644
--- a/tensorflow/contrib/gan/python/train_test.py
+++ b/tensorflow/contrib/gan/python/train_test.py
@@ -114,6 +114,12 @@ def stargan_generator_model(inputs, _):
return variable_scope.get_variable('dummy_g', initializer=0.5) * inputs
+class StarGANGenerator(object):
+
+ def __call__(self, inputs, _):
+ return stargan_generator_model(inputs, _)
+
+
def stargan_discriminator_model(inputs, num_domains):
"""Differentiable dummy discriminator for StarGAN."""
@@ -130,6 +136,12 @@ def stargan_discriminator_model(inputs, num_domains):
return output_src, output_cls
+class StarGANDiscriminator(object):
+
+ def __call__(self, inputs, num_domains):
+ return stargan_discriminator_model(inputs, num_domains)
+
+
def get_gan_model():
# TODO(joelshor): Find a better way of creating a variable scope.
with variable_scope.variable_scope('generator') as gen_scope:
@@ -272,6 +284,49 @@ def create_callable_cyclegan_model():
data_y=array_ops.ones([1, 2]))
+def get_stargan_model():
+ """Similar to get_gan_model()."""
+ # TODO(joelshor): Find a better way of creating a variable scope.
+ with variable_scope.variable_scope('generator') as gen_scope:
+ pass
+ with variable_scope.variable_scope('discriminator') as dis_scope:
+ pass
+ return namedtuples.StarGANModel(
+ input_data=array_ops.ones([1, 2, 2, 3]),
+ input_data_domain_label=array_ops.ones([1, 2]),
+ generated_data=array_ops.ones([1, 2, 2, 3]),
+ generated_data_domain_target=array_ops.ones([1, 2]),
+ reconstructed_data=array_ops.ones([1, 2, 2, 3]),
+ discriminator_input_data_source_predication=array_ops.ones([1]),
+ discriminator_generated_data_source_predication=array_ops.ones([1]),
+ discriminator_input_data_domain_predication=array_ops.ones([1, 2]),
+ discriminator_generated_data_domain_predication=array_ops.ones([1, 2]),
+ generator_variables=None,
+ generator_scope=gen_scope,
+ generator_fn=stargan_generator_model,
+ discriminator_variables=None,
+ discriminator_scope=dis_scope,
+ discriminator_fn=stargan_discriminator_model)
+
+
+def get_callable_stargan_model():
+ model = get_stargan_model()
+ return model._replace(
+ generator_fn=StarGANGenerator(), discriminator_fn=StarGANDiscriminator())
+
+
+def create_stargan_model():
+ return train.stargan_model(
+ stargan_generator_model, stargan_discriminator_model,
+ array_ops.ones([1, 2, 2, 3]), array_ops.ones([1, 2]))
+
+
+def create_callable_stargan_model():
+ return train.stargan_model(StarGANGenerator(), StarGANDiscriminator(),
+ array_ops.ones([1, 2, 2, 3]),
+ array_ops.ones([1, 2]))
+
+
def get_sync_optimizer():
return sync_replicas_optimizer.SyncReplicasOptimizer(
gradient_descent.GradientDescentOptimizer(learning_rate=1.0),
@@ -292,6 +347,8 @@ class GANModelTest(test.TestCase, parameterized.TestCase):
('cyclegan', get_cyclegan_model, namedtuples.CycleGANModel),
('callable_cyclegan', get_callable_cyclegan_model,
namedtuples.CycleGANModel),
+ ('stargan', get_stargan_model, namedtuples.StarGANModel),
+ ('callabel_stargan', get_callable_stargan_model, namedtuples.StarGANModel)
)
def test_output_type(self, create_fn, expected_tuple_type):
"""Test that output type is as expected."""
@@ -609,6 +666,27 @@ class GANLossTest(test.TestCase, parameterized.TestCase):
self.assertTrue(np.isscalar(loss_y2x_dis_np))
@parameterized.named_parameters(
+ ('notcallable', create_stargan_model),
+ ('callable', create_callable_stargan_model),
+ )
+ def test_stargan(self, create_gan_model_fn):
+
+ model = create_gan_model_fn()
+ model_loss = train.stargan_loss(model)
+
+ self.assertIsInstance(model_loss, namedtuples.GANLoss)
+
+ with self.test_session() as sess:
+
+ sess.run(variables.global_variables_initializer())
+
+ gen_loss, disc_loss = sess.run(
+ [model_loss.generator_loss, model_loss.discriminator_loss])
+
+ self.assertTrue(np.isscalar(gen_loss))
+ self.assertTrue(np.isscalar(disc_loss))
+
+ @parameterized.named_parameters(
('gan', create_gan_model),
('callable_gan', create_callable_gan_model),
('infogan', create_infogan_model),
diff --git a/tensorflow/contrib/layers/__init__.py b/tensorflow/contrib/layers/__init__.py
index bc33596935..a7b41b714f 100644
--- a/tensorflow/contrib/layers/__init__.py
+++ b/tensorflow/contrib/layers/__init__.py
@@ -121,6 +121,7 @@ from tensorflow.contrib.layers.python.layers import *
from tensorflow.python.util.all_util import remove_undocumented
_allowed_symbols = ['bias_add',
+ 'conv1d',
'conv2d',
'conv3d',
'elu',
diff --git a/tensorflow/contrib/layers/python/layers/layers.py b/tensorflow/contrib/layers/python/layers/layers.py
index dd602cf3a9..fa334070ad 100644
--- a/tensorflow/contrib/layers/python/layers/layers.py
+++ b/tensorflow/contrib/layers/python/layers/layers.py
@@ -55,9 +55,9 @@ from tensorflow.python.training import moving_averages
# TODO(b/28426988): Replace legacy_* fns migrated from slim.
# TODO(b/28426988): Remove legacy_* when all uses have migrated to new API.
__all__ = [
- 'avg_pool2d', 'avg_pool3d', 'batch_norm', 'bias_add', 'conv2d', 'conv3d',
- 'conv2d_in_plane', 'conv2d_transpose', 'conv3d_transpose', 'convolution',
- 'convolution1d', 'convolution2d', 'convolution2d_in_plane',
+ 'avg_pool2d', 'avg_pool3d', 'batch_norm', 'bias_add', 'conv1d', 'conv2d',
+ 'conv3d', 'conv2d_in_plane', 'conv2d_transpose', 'conv3d_transpose',
+ 'convolution', 'convolution1d', 'convolution2d', 'convolution2d_in_plane',
'convolution2d_transpose', 'convolution3d', 'convolution3d_transpose',
'dense_to_sparse', 'dropout', 'elu', 'flatten', 'fully_connected', 'GDN',
'gdn', 'images_to_sequence', 'layer_norm', 'linear', 'pool', 'max_pool2d',
@@ -3320,6 +3320,7 @@ relu6 = functools.partial(fully_connected, activation_fn=nn.relu6)
linear = functools.partial(fully_connected, activation_fn=None)
# Simple alias.
+conv1d = convolution1d
conv2d = convolution2d
conv3d = convolution3d
conv2d_transpose = convolution2d_transpose
diff --git a/tensorflow/contrib/learn/python/learn/estimators/run_config.py b/tensorflow/contrib/learn/python/learn/estimators/run_config.py
index 7cb87619d9..c36879e048 100644
--- a/tensorflow/contrib/learn/python/learn/estimators/run_config.py
+++ b/tensorflow/contrib/learn/python/learn/estimators/run_config.py
@@ -302,6 +302,7 @@ class RunConfig(ClusterConfig, core_run_config.RunConfig):
# so instead of breaking compatibility with that assumption, we
# just manually initialize this field:
self._train_distribute = None
+ self._eval_distribute = None
self._device_fn = None
gpu_options = config_pb2.GPUOptions(
diff --git a/tensorflow/contrib/lite/allocation.cc b/tensorflow/contrib/lite/allocation.cc
index c42622ff02..ef6c14f085 100644
--- a/tensorflow/contrib/lite/allocation.cc
+++ b/tensorflow/contrib/lite/allocation.cc
@@ -99,7 +99,9 @@ FileCopyAllocation::FileCopyAllocation(const char* filename,
filename);
return;
}
- copied_buffer_ = std::move(buffer);
+ // Versions of GCC before 6.2.0 don't support std::move from non-const
+ // char[] to const char[] unique_ptrs.
+ copied_buffer_.reset(const_cast<char const*>(buffer.release()));
}
FileCopyAllocation::~FileCopyAllocation() {}
diff --git a/tensorflow/contrib/lite/build_def.bzl b/tensorflow/contrib/lite/build_def.bzl
index 7c13f9011e..422584c0ea 100644
--- a/tensorflow/contrib/lite/build_def.bzl
+++ b/tensorflow/contrib/lite/build_def.bzl
@@ -2,8 +2,8 @@
load(
"//tensorflow:tensorflow.bzl",
- "tf_cc_test",
"tf_cc_shared_object",
+ "tf_cc_test",
)
def tflite_copts():
@@ -27,6 +27,9 @@ def tflite_copts():
str(Label("//tensorflow:ios_x86_64")): [
"-msse4.1",
],
+ str(Label("//tensorflow:windows")): [
+ "/DTF_COMPILE_LIBRARY",
+ ],
"//conditions:default": [],
}) + select({
str(Label("//tensorflow:with_default_optimizations")): [],
@@ -53,6 +56,7 @@ def tflite_linkopts_unstripped():
"-Wl,--gc-sections", # Eliminate unused code and data.
"-Wl,--as-needed", # Don't link unused libs.
],
+ "//tensorflow:darwin": [],
"//tensorflow/contrib/lite:mips": [],
"//tensorflow/contrib/lite:mips64": [],
"//conditions:default": [
@@ -74,6 +78,7 @@ def tflite_jni_linkopts_unstripped():
"-Wl,--gc-sections", # Eliminate unused code and data.
"-Wl,--as-needed", # Don't link unused libs.
],
+ "//tensorflow:darwin": [],
"//tensorflow/contrib/lite:mips": [],
"//tensorflow/contrib/lite:mips64": [],
"//conditions:default": [
@@ -122,19 +127,21 @@ def tflite_jni_binary(
linkopts = linkopts,
)
-def tflite_cc_shared_object(name,
- copts=tflite_copts(),
- linkopts=[],
- linkstatic=1,
- deps=[]):
- """Builds a shared object for TFLite."""
- tf_cc_shared_object(
- name=name,
- copts=copts,
- linkstatic=linkstatic,
- linkopts=linkopts + tflite_jni_linkopts(),
- framework_so=[],
- deps=deps)
+def tflite_cc_shared_object(
+ name,
+ copts = tflite_copts(),
+ linkopts = [],
+ linkstatic = 1,
+ deps = []):
+ """Builds a shared object for TFLite."""
+ tf_cc_shared_object(
+ name = name,
+ copts = copts,
+ linkstatic = linkstatic,
+ linkopts = linkopts + tflite_jni_linkopts(),
+ framework_so = [],
+ deps = deps,
+ )
def tf_to_tflite(name, src, options, out):
"""Convert a frozen tensorflow graphdef to TF Lite's flatbuffer.
@@ -240,6 +247,7 @@ def generated_test_models():
"local_response_norm",
"log_softmax",
"log",
+ "logical_or",
"lstm",
"max_pool",
"maximum",
@@ -248,13 +256,14 @@ def generated_test_models():
"mul",
"neg",
"not_equal",
+ "one_hot",
"pack",
"pad",
"padv2",
"prelu",
"pow",
"reduce_max",
- "reduce_prod",
+ #"reduce_prod", # disabled due to b/111823366
"relu",
"relu1",
"relu6",
diff --git a/tensorflow/contrib/lite/builtin_op_data.h b/tensorflow/contrib/lite/builtin_op_data.h
index fd16aa1063..70178b2faa 100644
--- a/tensorflow/contrib/lite/builtin_op_data.h
+++ b/tensorflow/contrib/lite/builtin_op_data.h
@@ -282,6 +282,10 @@ typedef struct {
int axis;
} TfLitePackParams;
+typedef struct {
+ int axis;
+} TfLiteOneHotParams;
+
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
diff --git a/tensorflow/contrib/lite/builtin_ops.h b/tensorflow/contrib/lite/builtin_ops.h
index 1ae73b9738..0b6568fd2f 100644
--- a/tensorflow/contrib/lite/builtin_ops.h
+++ b/tensorflow/contrib/lite/builtin_ops.h
@@ -110,6 +110,7 @@ typedef enum {
kTfLiteBuiltinReduceMax = 82,
kTfLiteBuiltinPack = 83,
kTfLiteBuiltinLogicalOr = 84,
+ kTfLiteBuiltinOneHot = 85,
} TfLiteBuiltinOperator;
#ifdef __cplusplus
diff --git a/tensorflow/contrib/lite/context.h b/tensorflow/contrib/lite/context.h
index cbfce12d7e..5bc20106d3 100644
--- a/tensorflow/contrib/lite/context.h
+++ b/tensorflow/contrib/lite/context.h
@@ -29,6 +29,9 @@ limitations under the License.
#ifndef TENSORFLOW_CONTRIB_LITE_CONTEXT_H_
#define TENSORFLOW_CONTRIB_LITE_CONTEXT_H_
+#if defined(_MSC_VER)
+#include <complex.h>
+#endif
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
@@ -180,7 +183,11 @@ typedef union {
uint8_t* uint8;
bool* b;
int16_t* i16;
+#if defined(_MSC_VER)
+ _Fcomplex* c64;
+#else
_Complex float* c64;
+#endif
} TfLitePtrUnion;
// Memory allocation strategies. kTfLiteMmapRo is for read-only memory-mapped
diff --git a/tensorflow/contrib/lite/delegates/eager/BUILD b/tensorflow/contrib/lite/delegates/eager/BUILD
index 03a4b7bf1d..a28707382e 100644
--- a/tensorflow/contrib/lite/delegates/eager/BUILD
+++ b/tensorflow/contrib/lite/delegates/eager/BUILD
@@ -68,6 +68,43 @@ cc_test(
)
cc_library(
+ name = "kernel",
+ srcs = ["kernel.cc"],
+ hdrs = ["kernel.h"],
+ deps = [
+ ":delegate_data",
+ ":util",
+ "//tensorflow/contrib/lite:framework",
+ "//tensorflow/contrib/lite:kernel_api",
+ "//tensorflow/contrib/lite/kernels:kernel_util",
+ "//tensorflow/core:protos_all_cc",
+ "//tensorflow/core/common_runtime/eager:context",
+ "//tensorflow/core/common_runtime/eager:execute",
+ "//tensorflow/core/common_runtime/eager:tensor_handle",
+ "@flatbuffers",
+ ],
+)
+
+cc_test(
+ name = "kernel_test",
+ size = "small",
+ srcs = ["kernel_test.cc"],
+ tags = [
+ "no_oss",
+ "tflite_not_portable",
+ ],
+ deps = [
+ ":delegate_data",
+ ":kernel",
+ "//tensorflow/contrib/lite/kernels:test_util",
+ "//tensorflow/contrib/lite/testing:util",
+ "@com_google_absl//absl/memory",
+ "@com_google_googletest//:gtest",
+ "@flatbuffers",
+ ],
+)
+
+cc_library(
name = "util",
srcs = ["util.cc"],
hdrs = ["util.h"],
diff --git a/tensorflow/contrib/lite/delegates/eager/buffer_map.cc b/tensorflow/contrib/lite/delegates/eager/buffer_map.cc
index 1d6453f498..e5a19c3997 100644
--- a/tensorflow/contrib/lite/delegates/eager/buffer_map.cc
+++ b/tensorflow/contrib/lite/delegates/eager/buffer_map.cc
@@ -91,6 +91,10 @@ void BufferMap::SetFromTfLite(int tensor_index, const TfLiteTensor* tensor) {
for (int i = 0; i < num_dims; ++i) {
shape.AddDim(tensor->dims->data[i]);
}
+ // TODO(ahentz): we assume this is a new tensor and allocate a new buffer
+ // for it. This is not always the best approach. For example, this might
+ // be a reallocation after resizing tensors. In that case we would be
+ // preferable to somehow reuse the buffer.
auto* buf = new TfLiteTensorBuffer(tensor);
tensorflow::Tensor t = tensorflow::TensorCApi::MakeTensor(
GetTensorFlowDataType(tensor->type), shape, buf);
diff --git a/tensorflow/contrib/lite/delegates/eager/delegate_data.cc b/tensorflow/contrib/lite/delegates/eager/delegate_data.cc
index 29687694bd..0fd5c976f8 100644
--- a/tensorflow/contrib/lite/delegates/eager/delegate_data.cc
+++ b/tensorflow/contrib/lite/delegates/eager/delegate_data.cc
@@ -23,7 +23,8 @@ tensorflow::Status DelegateData::Create(std::unique_ptr<DelegateData>* data) {
std::vector<tensorflow::Device*> devices;
TF_RETURN_IF_ERROR(tensorflow::DeviceFactory::AddDevices(
- tensorflow::SessionOptions(), "/device:cpu:*", &devices));
+ tensorflow::SessionOptions(), "/job:localhost/replica:0/task:0",
+ &devices));
std::unique_ptr<tensorflow::DeviceMgr> device_mgr(
new tensorflow::DeviceMgr(devices));
diff --git a/tensorflow/contrib/lite/delegates/eager/kernel.cc b/tensorflow/contrib/lite/delegates/eager/kernel.cc
new file mode 100644
index 0000000000..1727981807
--- /dev/null
+++ b/tensorflow/contrib/lite/delegates/eager/kernel.cc
@@ -0,0 +1,289 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#include "tensorflow/contrib/lite/delegates/eager/kernel.h"
+
+#include "third_party/flatbuffers/include/flatbuffers/flexbuffers.h"
+#include "tensorflow/contrib/lite/builtin_ops.h"
+#include "tensorflow/contrib/lite/context.h"
+#include "tensorflow/contrib/lite/context_util.h"
+#include "tensorflow/contrib/lite/delegates/eager/delegate_data.h"
+#include "tensorflow/contrib/lite/delegates/eager/util.h"
+#include "tensorflow/contrib/lite/kernels/kernel_util.h"
+#include "tensorflow/core/common_runtime/eager/context.h"
+#include "tensorflow/core/common_runtime/eager/execute.h"
+#include "tensorflow/core/common_runtime/eager/tensor_handle.h"
+#include "tensorflow/core/framework/node_def.pb.h"
+
+// Note: this is part of TF Lite's Eager delegation code which is to be
+// completed soon.
+
+// This is the TF Lite op that is created by the eager delegate to handle
+// execution of a supported subgraph. The usual flow is that the delegate
+// informs the interpreter of supported nodes in a graph, and each supported
+// subgraph is replaced with one instance of this kernel.
+//
+// The kernel is initialized with TfLiteDelegateParams from which we retrieve
+// the global EagerContext and BufferMap, as well as a list of inputs and
+// outputs to the subgraph. Those are used to build the OpData, with a list of
+// TensorFlow Ops that should be executed in order (which we call an OpNode).
+//
+// For each node included in the subgraph, we query the interpreter and
+// retrieve the associated NodeDef, which is then used to configure the
+// corresponding TensorFlow/Eager Op.
+
+namespace tflite {
+namespace eager {
+namespace kernel {
+
+// Controls the lifetime of tensor handles in a vector.
+class VectorOfHandles {
+ public:
+ explicit VectorOfHandles(int num_elements) : vector_(num_elements, nullptr) {}
+
+ ~VectorOfHandles() {
+ for (auto* handle : vector_) {
+ if (handle) handle->Unref();
+ }
+ }
+
+ tensorflow::gtl::InlinedVector<tensorflow::TensorHandle*, 2>* GetVector() {
+ return &vector_;
+ }
+
+ tensorflow::TensorHandle* GetHandle(int index) { return vector_[index]; }
+
+ private:
+ tensorflow::gtl::InlinedVector<tensorflow::TensorHandle*, 2> vector_;
+};
+
+// Executes the TensorFlow op given by 'op_name', with the attributes specified
+// in 'nodedef'. Inputs and outputs are given as indices into the 'buffer_map'.
+tensorflow::Status ExecuteEagerOp(tensorflow::EagerContext* eager_context,
+ BufferMap* buffer_map, const string& op_name,
+ const tensorflow::NodeDef& nodedef,
+ const std::vector<int>& inputs,
+ const std::vector<int>& outputs) {
+ const tensorflow::AttrTypeMap* attr_types;
+ TF_RETURN_WITH_CONTEXT_IF_ERROR(
+ tensorflow::AttrTypeMapForOp(op_name.c_str(), &attr_types),
+ " (while processing attributes of '", op_name, "')");
+
+ tensorflow::EagerOperation op(eager_context, op_name.c_str(), attr_types);
+ for (const auto& attr : nodedef.attr()) {
+ op.MutableAttrs()->Set(attr.first, attr.second);
+ }
+
+ for (int input_index : inputs) {
+ if (!buffer_map->HasTensor(input_index)) {
+ return tensorflow::errors::Internal(
+ "Cannot read from invalid tensor index ", input_index);
+ }
+ auto* handle = new tensorflow::TensorHandle(
+ buffer_map->GetTensor(input_index), nullptr, nullptr, nullptr);
+ op.AddInput(handle);
+ handle->Unref();
+ }
+
+ int num_retvals = outputs.size();
+ VectorOfHandles retvals(num_retvals);
+ TF_RETURN_WITH_CONTEXT_IF_ERROR(
+ EagerExecute(&op, retvals.GetVector(), &num_retvals),
+ " (while executing '", op_name, "' via Eager)");
+
+ if (num_retvals != outputs.size()) {
+ return tensorflow::errors::Internal(
+ "Unexpected number of outputs from EagerExecute");
+ }
+
+ for (int i = 0; i < num_retvals; ++i) {
+ const tensorflow::Tensor* tensor = nullptr;
+ TF_RETURN_IF_ERROR(retvals.GetHandle(i)->Tensor(&tensor));
+ buffer_map->SetFromTensorFlow(outputs[i], *tensor);
+ }
+
+ return tensorflow::Status::OK();
+}
+
+// A single node within the larger 'op'. Note that this kernel executes many
+// TensorFlow ops within a single TF Lite op.
+struct OpNode {
+ // The name of the TensorFlow op to execute.
+ string name;
+ // The corresponding NodeDef, containing the attributes for the op.
+ tensorflow::NodeDef nodedef;
+ // List of inputs, as TF Lite tensor indices.
+ std::vector<int> inputs;
+ // List of outputs, as TF Lite tensor indices.
+ std::vector<int> outputs;
+};
+
+// The Larger 'op', which contains all the nodes in a supported subgraph.
+struct OpData {
+ tensorflow::EagerContext* eager_context;
+ BufferMap* buffer_map;
+ std::vector<OpNode> nodes;
+ std::vector<int> subgraph_inputs;
+ std::vector<int> subgraph_outputs;
+};
+
+void* Init(TfLiteContext* context, const char* buffer, size_t length) {
+ auto* op_data = new OpData;
+
+ const TfLiteDelegateParams* params =
+ reinterpret_cast<const TfLiteDelegateParams*>(buffer);
+ CHECK(params);
+ CHECK(params->delegate);
+ CHECK(params->delegate->data_);
+ op_data->eager_context =
+ reinterpret_cast<DelegateData*>(params->delegate->data_)
+ ->GetEagerContext();
+ op_data->buffer_map =
+ reinterpret_cast<DelegateData*>(params->delegate->data_)->GetBufferMap();
+
+ CHECK(params->output_tensors);
+ for (auto tensor_index : TfLiteIntArrayView(params->output_tensors)) {
+ op_data->subgraph_outputs.push_back(tensor_index);
+ }
+
+ CHECK(params->input_tensors);
+ for (auto tensor_index : TfLiteIntArrayView(params->input_tensors)) {
+ op_data->subgraph_inputs.push_back(tensor_index);
+ }
+
+ CHECK(params->nodes_to_replace);
+ for (auto node_index : TfLiteIntArrayView(params->nodes_to_replace)) {
+ TfLiteNode* node;
+ TfLiteRegistration* reg;
+ context->GetNodeAndRegistration(context, node_index, &node, &reg);
+
+ op_data->nodes.push_back(OpNode());
+ OpNode& node_data = op_data->nodes.back();
+
+ node_data.name = "";
+ if (node->custom_initial_data) {
+ // The flexbuffer contains a vector where the first elements is the
+ // op name and the second is a serialized NodeDef.
+ const flexbuffers::Vector& v =
+ flexbuffers::GetRoot(
+ reinterpret_cast<const uint8_t*>(node->custom_initial_data),
+ node->custom_initial_data_size)
+ .AsVector();
+
+ node_data.name = v[0].AsString().str();
+ if (!node_data.nodedef.ParseFromString(v[1].AsString().str())) {
+ // We will just leave the nodedef empty and error out in Eval().
+ node_data.nodedef.Clear();
+ }
+ }
+
+ for (auto input_index : TfLiteIntArrayView(node->inputs)) {
+ node_data.inputs.push_back(input_index);
+ }
+ for (auto output_index : TfLiteIntArrayView(node->outputs)) {
+ node_data.outputs.push_back(output_index);
+ }
+ }
+
+ return op_data;
+}
+
+void Free(TfLiteContext* context, void* buffer) {
+ delete reinterpret_cast<OpData*>(buffer);
+}
+
+TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
+ const auto* op_data = reinterpret_cast<OpData*>(node->user_data);
+ TF_LITE_ENSURE_MSG(
+ context, op_data->eager_context != nullptr,
+ "Failed to initialize eager context. This often happens when a CPU "
+ "device has not been registered, presumably because some symbols from "
+ "tensorflow/core:core_cpu_impl were not linked into the binary.");
+
+ // Whenever we find a constant tensor, insert it in the buffer map.
+ BufferMap* buffer_map = op_data->buffer_map;
+ for (auto tensor_index : op_data->subgraph_inputs) {
+ TfLiteTensor* tensor = &context->tensors[tensor_index];
+ if (IsConstantTensor(tensor)) {
+ if (!buffer_map->HasTensor(tensor_index)) {
+ buffer_map->SetFromTfLite(tensor_index, tensor);
+ }
+ }
+ }
+
+ // All output tensors are allocated by TensorFlow/Eager, so we
+ // mark them as kTfLiteDynamic.
+ for (auto tensor_index : op_data->subgraph_outputs) {
+ SetTensorToDynamic(&context->tensors[tensor_index]);
+ }
+
+ return kTfLiteOk;
+}
+
+TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
+ const auto* op_data = reinterpret_cast<OpData*>(node->user_data);
+ BufferMap* buffer_map = op_data->buffer_map;
+ tensorflow::EagerContext* eager_context = op_data->eager_context;
+
+ // Insert a tensor in the buffer map for all inputs that are not constant.
+ // Constants were handled in Prepare() already.
+ for (auto tensor_index : op_data->subgraph_inputs) {
+ TfLiteTensor* tensor = &context->tensors[tensor_index];
+ if (!IsConstantTensor(tensor)) {
+ buffer_map->SetFromTfLite(tensor_index, tensor);
+ }
+ }
+
+ // Execute the TensorFlow Ops sequentially.
+ for (const auto& node_data : op_data->nodes) {
+ if (node_data.nodedef.op().empty()) {
+ context->ReportError(context, "Invalid NodeDef in Eager op '%s'",
+ node_data.name.c_str());
+ return kTfLiteError;
+ }
+ auto status =
+ ExecuteEagerOp(eager_context, buffer_map, node_data.name,
+ node_data.nodedef, node_data.inputs, node_data.outputs);
+ TF_LITE_ENSURE_OK(context, ConvertStatus(context, status));
+ }
+
+ for (auto tensor_index : op_data->subgraph_outputs) {
+ if (!buffer_map->HasTensor(tensor_index)) {
+ context->ReportError(context, "Cannot write to invalid tensor index %d",
+ tensor_index);
+ return kTfLiteError;
+ }
+
+ TfLiteTensor* tensor = &context->tensors[tensor_index];
+ TF_LITE_ENSURE_OK(
+ context,
+ CopyShape(context, buffer_map->GetTensor(tensor_index), tensor));
+ tensor->buffer_handle = tensor_index;
+ tensor->data_is_stale = true;
+ }
+
+ return kTfLiteOk;
+}
+
+} // namespace kernel
+
+TfLiteRegistration GetKernel() {
+ TfLiteRegistration registration{&kernel::Init, &kernel::Free,
+ &kernel::Prepare, &kernel::Eval,
+ nullptr, kTfLiteBuiltinDelegate};
+ return registration;
+}
+
+} // namespace eager
+} // namespace tflite
diff --git a/tensorflow/contrib/lite/delegates/eager/kernel.h b/tensorflow/contrib/lite/delegates/eager/kernel.h
new file mode 100644
index 0000000000..100672c82d
--- /dev/null
+++ b/tensorflow/contrib/lite/delegates/eager/kernel.h
@@ -0,0 +1,34 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#ifndef TENSORFLOW_CONTRIB_LITE_DELEGATES_EAGER_KERNEL_H_
+#define TENSORFLOW_CONTRIB_LITE_DELEGATES_EAGER_KERNEL_H_
+
+#include "tensorflow/contrib/lite/context.h"
+
+namespace tflite {
+namespace eager {
+
+// Return the registration object used to initialize and execute ops that will
+// be delegated to TensorFlow's Eager runtime. This TF Lite op is created by
+// the eager delegate to handle execution of a supported subgraph. The usual
+// flow is that the delegate informs the interpreter of supported nodes in a
+// graph, and each supported subgraph is replaced with one instance of this
+// kernel.
+TfLiteRegistration GetKernel();
+
+} // namespace eager
+} // namespace tflite
+
+#endif // TENSORFLOW_CONTRIB_LITE_DELEGATES_EAGER_KERNEL_H_
diff --git a/tensorflow/contrib/lite/delegates/eager/kernel_test.cc b/tensorflow/contrib/lite/delegates/eager/kernel_test.cc
new file mode 100644
index 0000000000..7d9dddef93
--- /dev/null
+++ b/tensorflow/contrib/lite/delegates/eager/kernel_test.cc
@@ -0,0 +1,351 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#include "tensorflow/contrib/lite/delegates/eager/kernel.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "absl/memory/memory.h"
+#include "third_party/flatbuffers/include/flatbuffers/flexbuffers.h"
+#include "tensorflow/contrib/lite/delegates/eager/delegate_data.h"
+#include "tensorflow/contrib/lite/kernels/test_util.h"
+#include "tensorflow/contrib/lite/testing/util.h"
+
+namespace tflite {
+namespace eager {
+namespace {
+
+using tensorflow::protobuf::TextFormat;
+using ::testing::ContainsRegex;
+using ::testing::ElementsAre;
+
+// We will use these are custom_names, so they need to be static.
+static const char kIdentity[] = "Identity";
+static const char kUnpack[] = "Unpack";
+static const char kAdd[] = "Add";
+static const char kMul[] = "Mul";
+
+TfLiteStatus GenericPrepare(TfLiteContext* context, TfLiteDelegate* delegate,
+ const std::vector<int>& supported_nodes) {
+ TfLiteIntArray* size_and_nodes =
+ ConvertVectorToTfLiteIntArray(supported_nodes);
+ TF_LITE_ENSURE_STATUS(context->ReplaceSubgraphsWithDelegateKernels(
+ context, eager::GetKernel(), size_and_nodes, delegate));
+ TfLiteIntArrayFree(size_and_nodes);
+ return kTfLiteOk;
+}
+
+class KernelTest : public ::testing::Test {
+ public:
+ KernelTest() {
+ CHECK(DelegateData::Create(&delegate_data_).ok());
+ interpreter_.reset(new Interpreter(&error_reporter_));
+ }
+
+ bool Invoke() { return interpreter_->Invoke() == kTfLiteOk; }
+
+ void SetValues(int tensor_index, const std::vector<float>& values) {
+ float* v = interpreter_->typed_tensor<float>(tensor_index);
+ for (float f : values) {
+ *v++ = f;
+ }
+ }
+
+ std::vector<float> GetValues(int tensor_index) {
+ TfLiteTensor* o = interpreter_->tensor(tensor_index);
+ return std::vector<float>(o->data.f, o->data.f + o->bytes / sizeof(float));
+ }
+
+ void SetShape(int tensor_index, const std::vector<int>& values) {
+ ASSERT_EQ(interpreter_->ResizeInputTensor(tensor_index, values), kTfLiteOk);
+ ASSERT_EQ(interpreter_->AllocateTensors(), kTfLiteOk);
+ }
+
+ std::vector<int> GetShape(int tensor_index) {
+ std::vector<int> result;
+ auto* dims = interpreter_->tensor(tensor_index)->dims;
+ for (int i = 0; i < dims->size; ++i) {
+ result.push_back(dims->data[i]);
+ }
+ return result;
+ }
+
+ template <typename T>
+ void ConfigureDelegate(T prepare_function) {
+ delegate_.data_ = delegate_data_.get();
+ delegate_.FreeBufferHandle = nullptr;
+ delegate_.Prepare = prepare_function;
+ delegate_.CopyFromBufferHandle = [](TfLiteDelegate* delegate,
+ TfLiteBufferHandle buffer_handle,
+ void* data, size_t size) {
+ auto* delegate_data = reinterpret_cast<DelegateData*>(delegate->data_);
+ tensorflow::StringPiece values =
+ delegate_data->GetBufferMap()->GetTensor(buffer_handle).tensor_data();
+ memcpy(data, values.data(), values.size());
+ return kTfLiteOk;
+ };
+ CHECK(interpreter_->ModifyGraphWithDelegate(
+ &delegate_, /*allow_dynamic_tensors=*/true) == kTfLiteOk);
+ }
+
+ void AddOp(const char* name, const std::vector<int>& inputs,
+ const std::vector<int>& outputs) {
+ auto attr = [](const string& key, const string& value) {
+ return " attr{ key: '" + key + "' value {" + value + "}}";
+ };
+
+ string attributes;
+ if (name == string(kUnpack)) {
+ attributes = attr("T", "type: DT_FLOAT") + attr("num", "i: 2") +
+ attr("axis", "i: 0");
+ } else if (name == string(kIdentity)) {
+ attributes = attr("T", "type: DT_FLOAT");
+ } else if (name == string(kAdd)) {
+ attributes = attr("T", "type: DT_FLOAT");
+ } else if (name == string(kMul)) {
+ attributes = attr("T", "type: DT_FLOAT");
+ }
+ AddTfOp(name, attributes, inputs, outputs);
+ }
+
+ void AddTensors(int num_tensors, const std::vector<int>& inputs,
+ const std::vector<int>& outputs) {
+ interpreter_->AddTensors(num_tensors);
+ for (int i = 0; i < num_tensors; ++i) {
+ TfLiteQuantizationParams quant;
+ CHECK_EQ(interpreter_->SetTensorParametersReadWrite(i, kTfLiteFloat32,
+ /*name=*/"",
+ /*dims=*/{3}, quant),
+ kTfLiteOk);
+ }
+
+ CHECK_EQ(interpreter_->SetInputs(inputs), kTfLiteOk);
+ CHECK_EQ(interpreter_->SetOutputs(outputs), kTfLiteOk);
+ }
+
+ const TestErrorReporter& error_reporter() const { return error_reporter_; }
+
+ void AddTfLiteOp(const char* name, const std::vector<int>& inputs,
+ const std::vector<int>& outputs) {
+ CHECK_EQ(string(name), kMul); // can only add MUL
+ static TfLiteRegistration reg = {nullptr, nullptr, nullptr, nullptr};
+ reg.builtin_code = BuiltinOperator_MUL;
+ reg.prepare = [](TfLiteContext* context, TfLiteNode* node) {
+ auto* i0 = &context->tensors[node->inputs->data[0]];
+ auto* o = &context->tensors[node->outputs->data[0]];
+ return context->ResizeTensor(context, o, TfLiteIntArrayCopy(i0->dims));
+ };
+ reg.invoke = [](TfLiteContext* context, TfLiteNode* node) {
+ auto* i0 = &context->tensors[node->inputs->data[0]];
+ auto* i1 = &context->tensors[node->inputs->data[1]];
+ auto* o = &context->tensors[node->outputs->data[0]];
+ for (int i = 0; i < o->bytes / sizeof(float); ++i) {
+ o->data.f[i] = i0->data.f[i] * i1->data.f[i];
+ }
+ return kTfLiteOk;
+ };
+
+ CHECK_EQ(interpreter_->AddNodeWithParameters(inputs, outputs, nullptr, 0,
+ nullptr, &reg),
+ kTfLiteOk);
+ }
+
+ private:
+ void AddTfOp(const char* name, const string& nodedef_str,
+ const std::vector<int>& inputs,
+ const std::vector<int>& outputs) {
+ static TfLiteRegistration reg = {nullptr, nullptr, nullptr, nullptr};
+ reg.builtin_code = BuiltinOperator_CUSTOM;
+ reg.custom_name = name;
+
+ tensorflow::NodeDef nodedef;
+ CHECK(TextFormat::ParseFromString(nodedef_str + " op: '" + name + "'",
+ &nodedef));
+ string serialized_nodedef;
+ CHECK(nodedef.SerializeToString(&serialized_nodedef));
+ flexbuffers::Builder fbb;
+ fbb.Vector([&]() {
+ fbb.String(nodedef.op());
+ fbb.String(serialized_nodedef);
+ });
+ fbb.Finish();
+
+ flexbuffers_.push_back(fbb.GetBuffer());
+ auto& buffer = flexbuffers_.back();
+ CHECK_EQ(interpreter_->AddNodeWithParameters(
+ inputs, outputs, reinterpret_cast<const char*>(buffer.data()),
+ buffer.size(), nullptr, &reg),
+ kTfLiteOk);
+ }
+
+ std::unique_ptr<Interpreter> interpreter_;
+ std::unique_ptr<DelegateData> delegate_data_;
+ TfLiteDelegate delegate_;
+ std::vector<std::vector<uint8_t>> flexbuffers_;
+ TestErrorReporter error_reporter_;
+};
+
+TEST_F(KernelTest, FullGraph) {
+ // Define the graph.
+ AddTensors(9, {0, 3}, {8});
+
+ AddOp(kUnpack, {0}, {1, 2});
+ AddOp(kUnpack, {3}, {4, 5});
+ AddOp(kAdd, {1, 4}, {6});
+ AddOp(kAdd, {2, 5}, {7});
+ AddOp(kMul, {6, 7}, {8});
+
+ // Apply Delegate.
+ ConfigureDelegate([](TfLiteContext* context, TfLiteDelegate* delegate) {
+ return GenericPrepare(context, delegate, {0, 1, 2, 3, 4});
+ });
+
+ // Define inputs.
+ SetShape(0, {2, 2, 1});
+ SetValues(0, {1.1f, 2.2f, 3.3f, 4.4f});
+ SetShape(3, {2, 2, 1});
+ SetValues(3, {1.1f, 2.2f, 3.3f, 4.4f});
+
+ ASSERT_TRUE(Invoke());
+
+ ASSERT_THAT(GetShape(8), ElementsAre(2, 1));
+ ASSERT_THAT(GetValues(8), ElementsAre(14.52f, 38.72f));
+}
+
+TEST_F(KernelTest, BadTensorFlowOp) {
+ AddTensors(2, {0}, {1});
+ AddOp("NonExistentOp", {0}, {1});
+
+ ConfigureDelegate([](TfLiteContext* context, TfLiteDelegate* delegate) {
+ return GenericPrepare(context, delegate, {0});
+ });
+
+ SetShape(0, {2, 2, 1});
+ SetValues(0, {1.1f, 2.2f, 3.3f, 4.4f});
+
+ ASSERT_FALSE(Invoke());
+ ASSERT_THAT(error_reporter().error_messages(),
+ ContainsRegex("while processing attributes of 'NonExistentOp'"));
+}
+
+TEST_F(KernelTest, BadNumberOfOutputs) {
+ AddTensors(3, {0}, {1, 2});
+ AddOp(kIdentity, {0}, {1, 2});
+
+ ConfigureDelegate([](TfLiteContext* context, TfLiteDelegate* delegate) {
+ return GenericPrepare(context, delegate, {0});
+ });
+
+ SetShape(0, {2, 2, 1});
+ SetValues(0, {1.1f, 2.2f, 3.3f, 4.4f});
+
+ ASSERT_FALSE(Invoke());
+ ASSERT_THAT(error_reporter().error_messages(),
+ ContainsRegex("Unexpected number of outputs"));
+}
+
+TEST_F(KernelTest, IncompatibleNodeDef) {
+ AddTensors(2, {0}, {1});
+
+ // Cast is a TF op, but we don't add the proper nodedef to it in AddOp.
+ AddOp("Cast", {0}, {1});
+
+ ConfigureDelegate([](TfLiteContext* context, TfLiteDelegate* delegate) {
+ return GenericPrepare(context, delegate, {0});
+ });
+
+ SetShape(0, {2, 2, 1});
+ SetValues(0, {1.1f, 2.2f, 3.3f, 4.4f});
+
+ ASSERT_FALSE(Invoke());
+ ASSERT_THAT(error_reporter().error_messages(),
+ ContainsRegex("while executing 'Cast' via Eager"));
+}
+
+TEST_F(KernelTest, WrongSetOfNodes) {
+ AddTensors(4, {0}, {3});
+ AddOp(kUnpack, {0}, {1, 2});
+ AddTfLiteOp(kMul, {1, 2}, {3});
+
+ // Specify that kMul (#1) is supported when it actually isn't.
+ ConfigureDelegate([](TfLiteContext* context, TfLiteDelegate* delegate) {
+ return GenericPrepare(context, delegate, {0, 1});
+ });
+
+ SetShape(0, {2, 2, 1});
+ SetValues(0, {1.1f, 2.2f, 3.3f, 4.4f});
+
+ ASSERT_FALSE(Invoke());
+ ASSERT_THAT(error_reporter().error_messages(),
+ ContainsRegex("Invalid NodeDef in Eager op"));
+}
+
+TEST_F(KernelTest, MixedGraph) {
+ AddTensors(9, {0, 3}, {8});
+
+ AddOp(kUnpack, {0}, {1, 2});
+ AddOp(kUnpack, {3}, {4, 5});
+ AddOp(kAdd, {1, 4}, {6});
+ AddOp(kAdd, {2, 5}, {7});
+ AddTfLiteOp(kMul, {6, 7}, {8});
+
+ ConfigureDelegate([](TfLiteContext* context, TfLiteDelegate* delegate) {
+ return GenericPrepare(context, delegate, {0, 1, 2, 3});
+ });
+
+ SetShape(0, {2, 2, 1});
+ SetValues(0, {1.1f, 2.2f, 3.3f, 4.4f});
+ SetShape(3, {2, 2, 1});
+ SetValues(3, {1.1f, 2.2f, 3.3f, 4.4f});
+
+ ASSERT_TRUE(Invoke());
+
+ ASSERT_THAT(GetShape(8), ElementsAre(2, 1));
+ ASSERT_THAT(GetValues(8), ElementsAre(14.52f, 38.72f));
+}
+
+TEST_F(KernelTest, SplitGraph) {
+ AddTensors(10, {0}, {9});
+
+ AddOp(kUnpack, {0}, {1, 2});
+ AddOp(kAdd, {1, 2}, {3});
+ AddOp(kUnpack, {3}, {4, 5});
+
+ AddTfLiteOp(kMul, {4, 5}, {6});
+
+ AddOp(kUnpack, {6}, {7, 8});
+ AddOp(kAdd, {7, 8}, {9});
+
+ ConfigureDelegate([](TfLiteContext* context, TfLiteDelegate* delegate) {
+ return GenericPrepare(context, delegate, {0, 1, 2, 4, 5});
+ });
+
+ SetShape(0, {2, 2, 2, 1});
+ SetValues(0, {3.0f, 1.0f, 0.5f, -1.0f, 0.0f, 1.0f, 1.5f, 3.0f});
+
+ ASSERT_TRUE(Invoke());
+
+ ASSERT_THAT(GetShape(9), ElementsAre(1));
+ ASSERT_THAT(GetValues(9), ElementsAre(10.0f));
+}
+
+} // namespace
+} // namespace eager
+} // namespace tflite
+
+int main(int argc, char** argv) {
+ ::tflite::LogToStderr();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/tensorflow/contrib/lite/delegates/nnapi/nnapi_delegate.cc b/tensorflow/contrib/lite/delegates/nnapi/nnapi_delegate.cc
index 0c7f6d3125..60855eb8ed 100644
--- a/tensorflow/contrib/lite/delegates/nnapi/nnapi_delegate.cc
+++ b/tensorflow/contrib/lite/delegates/nnapi/nnapi_delegate.cc
@@ -436,7 +436,6 @@ class NNAPIDelegateKernel {
}
break;
case kTfLiteBuiltinSqueeze:
- // Squeeze requires NNAPI1.1.
if (version == 1 && kAndroidSdkVersion >= kMinSdkVersionForNNAPI11) {
return [](TfLiteContext* context, NNAPIOpBuilder* builder,
TfLiteNode* node) -> ANeuralNetworksOperationType {
@@ -464,9 +463,215 @@ class NNAPIDelegateKernel {
return ANEURALNETWORKS_L2_NORMALIZATION;
};
}
+ case kTfLiteBuiltinLocalResponseNormalization:
+ if (version == 1) {
+ return [](TfLiteContext* context, NNAPIOpBuilder* builder,
+ TfLiteNode* node) -> ANeuralNetworksOperationType {
+ auto builtin = reinterpret_cast<TfLiteLocalResponseNormParams*>(
+ node->builtin_data);
+ builder->AddScalarInt32Operand(builtin->radius);
+ builder->AddScalarFloat32Operand(builtin->bias);
+ builder->AddScalarFloat32Operand(builtin->alpha);
+ builder->AddScalarFloat32Operand(builtin->beta);
+ return ANEURALNETWORKS_LOCAL_RESPONSE_NORMALIZATION;
+ };
+ } else {
+ // TODO(miaowang): clean-up code and return early in the unsupported
+ // case.
+ return nullptr;
+ }
+ break;
+ case kTfLiteBuiltinLshProjection:
+ if (version == 1) {
+ // NNAPI does not support sparse projection correctly (b/111751836).
+ if (reinterpret_cast<TfLiteLSHProjectionParams*>(node->builtin_data)
+ ->type == kTfLiteLshProjectionSparse) {
+ return nullptr;
+ }
+ return [](TfLiteContext* context, NNAPIOpBuilder* builder,
+ TfLiteNode* node) -> ANeuralNetworksOperationType {
+ auto builtin = reinterpret_cast<TfLiteLSHProjectionParams*>(
+ node->builtin_data);
+ builder->AddScalarInt32Operand(builtin->type);
+ return ANEURALNETWORKS_LSH_PROJECTION;
+ };
+ } else {
+ return nullptr;
+ }
+ break;
+ case kTfLiteBuiltinConcatenation:
+ if (version == 1 &&
+ reinterpret_cast<TfLiteConcatenationParams*>(node->builtin_data)
+ ->activation == kTfLiteActNone) {
+ if (context->tensors[node->inputs->data[0]].type == kTfLiteUInt8) {
+ // NNAPI only support concatenating quantized tensor of the same
+ // scale and offset.
+ auto first_param = context->tensors[node->inputs->data[0]].params;
+ for (int i = 0; i < node->inputs->size; i++) {
+ auto curr_param = context->tensors[node->inputs->data[i]].params;
+ if (curr_param.scale != first_param.scale ||
+ curr_param.zero_point != first_param.zero_point) {
+ return nullptr;
+ }
+ }
+ }
+ return [](TfLiteContext* context, NNAPIOpBuilder* builder,
+ TfLiteNode* node) -> ANeuralNetworksOperationType {
+ auto builtin = reinterpret_cast<TfLiteConcatenationParams*>(
+ node->builtin_data);
+ builder->AddScalarInt32Operand(builtin->axis);
+ return ANEURALNETWORKS_CONCATENATION;
+ };
+ } else {
+ return nullptr;
+ }
+ break;
+ case kTfLiteBuiltinDequantize:
+ if (version == 1) {
+ return [](TfLiteContext* context, NNAPIOpBuilder* builder,
+ TfLiteNode* node) -> ANeuralNetworksOperationType {
+ return ANEURALNETWORKS_DEQUANTIZE;
+ };
+ } else {
+ return nullptr;
+ }
+ break;
+ case kTfLiteBuiltinFloor:
+ if (version == 1) {
+ return [](TfLiteContext* context, NNAPIOpBuilder* builder,
+ TfLiteNode* node) -> ANeuralNetworksOperationType {
+ return ANEURALNETWORKS_FLOOR;
+ };
+ } else {
+ return nullptr;
+ }
+ break;
+ case kTfLiteBuiltinRelu:
+ if (version == 1) {
+ return [](TfLiteContext* context, NNAPIOpBuilder* builder,
+ TfLiteNode* node) -> ANeuralNetworksOperationType {
+ return ANEURALNETWORKS_RELU;
+ };
+ } else {
+ return nullptr;
+ }
+ break;
+ case kTfLiteBuiltinReluN1To1:
+ if (version == 1) {
+ return [](TfLiteContext* context, NNAPIOpBuilder* builder,
+ TfLiteNode* node) -> ANeuralNetworksOperationType {
+ return ANEURALNETWORKS_RELU1;
+ };
+ } else {
+ return nullptr;
+ }
+ break;
+ case kTfLiteBuiltinRelu6:
+ if (version == 1) {
+ return [](TfLiteContext* context, NNAPIOpBuilder* builder,
+ TfLiteNode* node) -> ANeuralNetworksOperationType {
+ return ANEURALNETWORKS_RELU6;
+ };
+ } else {
+ return nullptr;
+ }
+ break;
+ case kTfLiteBuiltinLogistic:
+ if (version == 1) {
+ return [](TfLiteContext* context, NNAPIOpBuilder* builder,
+ TfLiteNode* node) -> ANeuralNetworksOperationType {
+ return ANEURALNETWORKS_LOGISTIC;
+ };
+ } else {
+ return nullptr;
+ }
+ break;
+ case kTfLiteBuiltinTanh:
+ // TODO(miaowang): add additional checks for the parameters.
+ if (version == 1 &&
+ context->tensors[node->inputs->data[0]].type == kTfLiteFloat32) {
+ // NNAPI only support float tanh.
+ return [](TfLiteContext* context, NNAPIOpBuilder* builder,
+ TfLiteNode* node) -> ANeuralNetworksOperationType {
+ return ANEURALNETWORKS_TANH;
+ };
+ } else {
+ return nullptr;
+ }
+ break;
+ case kTfLiteBuiltinSub:
+ if (version == 1 && kAndroidSdkVersion >= kMinSdkVersionForNNAPI11 &&
+ context->tensors[node->inputs->data[0]].type == kTfLiteFloat32) {
+ // NNAPI only support float sub.
+ return [](TfLiteContext* context, NNAPIOpBuilder* builder,
+ TfLiteNode* node) -> ANeuralNetworksOperationType {
+ auto builtin =
+ reinterpret_cast<TfLiteSubParams*>(node->builtin_data);
+ builder->AddScalarInt32Operand(builtin->activation);
+ return ANEURALNETWORKS_SUB;
+ };
+ } else {
+ return nullptr;
+ }
+ break;
+ case kTfLiteBuiltinDiv:
+ if (version == 1 && kAndroidSdkVersion >= kMinSdkVersionForNNAPI11 &&
+ context->tensors[node->inputs->data[0]].type == kTfLiteFloat32) {
+ // NNAPI only support float div.
+ return [](TfLiteContext* context, NNAPIOpBuilder* builder,
+ TfLiteNode* node) -> ANeuralNetworksOperationType {
+ auto builtin =
+ reinterpret_cast<TfLiteDivParams*>(node->builtin_data);
+ builder->AddScalarInt32Operand(builtin->activation);
+ return ANEURALNETWORKS_DIV;
+ };
+ } else {
+ return nullptr;
+ }
+ break;
+ case kTfLiteBuiltinPad:
+ if (version == 1 && kAndroidSdkVersion >= kMinSdkVersionForNNAPI11 &&
+ node->inputs->size == 2 &&
+ context->tensors[node->inputs->data[0]].type == kTfLiteFloat32) {
+ // NNAPI does not support specifying the padding value.
+ // NNAPI pads physical zero for quantized tensors, so only delegate
+ // float pad to NNAPI.
+ return [](TfLiteContext* context, NNAPIOpBuilder* builder,
+ TfLiteNode* node) -> ANeuralNetworksOperationType {
+ return ANEURALNETWORKS_PAD;
+ };
+ } else {
+ return nullptr;
+ }
+ break;
+ case kTfLiteBuiltinSpaceToBatchNd:
+ if (version == 1 && kAndroidSdkVersion >= kMinSdkVersionForNNAPI11) {
+ return [](TfLiteContext* context, NNAPIOpBuilder* builder,
+ TfLiteNode* node) -> ANeuralNetworksOperationType {
+ return ANEURALNETWORKS_SPACE_TO_BATCH_ND;
+ };
+ } else {
+ return nullptr;
+ }
+ break;
+ case kTfLiteBuiltinStridedSlice:
+ if (version == 1 && kAndroidSdkVersion >= kMinSdkVersionForNNAPI11) {
+ return [](TfLiteContext* context, NNAPIOpBuilder* builder,
+ TfLiteNode* node) -> ANeuralNetworksOperationType {
+ auto builtin =
+ reinterpret_cast<TfLiteStridedSliceParams*>(node->builtin_data);
+ builder->AddScalarInt32Operand(builtin->begin_mask);
+ builder->AddScalarInt32Operand(builtin->end_mask);
+ builder->AddScalarInt32Operand(builtin->shrink_axis_mask);
+ return ANEURALNETWORKS_STRIDED_SLICE;
+ };
+ } else {
+ return nullptr;
+ }
+ break;
case kTfLiteBuiltinTranspose:
- // Transpose requires NNAPI1.1. Also note that the permutation input
- // tensor value dictates the output dimensions.
+ // Note that the permutation input tensor value dictates the output
+ // dimensions.
// TODO(b/110888333): Support dynamically-sized tensors in delegates.
if ((version == 1) &&
(kAndroidSdkVersion >= kMinSdkVersionForNNAPI11) &&
diff --git a/tensorflow/contrib/lite/delegates/nnapi/nnapi_delegate_test.cc b/tensorflow/contrib/lite/delegates/nnapi/nnapi_delegate_test.cc
index baf8046f9b..b7b159c59f 100644
--- a/tensorflow/contrib/lite/delegates/nnapi/nnapi_delegate_test.cc
+++ b/tensorflow/contrib/lite/delegates/nnapi/nnapi_delegate_test.cc
@@ -82,7 +82,7 @@ TEST(NNAPIDelegate, AddWithRelu) {
EXPECT_THAT(m.GetOutput(), ElementsAreArray({0.0, 0.4, 1.0, 1.3}));
}
-class FloatMulOpModel : public SingleOpModel {
+class FloatMulOpModel : public SingleOpModelWithNNAPI {
public:
FloatMulOpModel(const TensorData& input1, const TensorData& input2,
const TensorData& output,
@@ -184,7 +184,7 @@ TEST(NNAPIDelegate, L2PoolWithNoActivation) {
EXPECT_THAT(m.GetOutput(), ElementsAreArray({3.5, 6.5}));
}
-class BaseConvolutionOpModel : public SingleOpModel {
+class BaseConvolutionOpModel : public SingleOpModelWithNNAPI {
public:
BaseConvolutionOpModel(
const TensorData& input, const TensorData& filter,
@@ -713,6 +713,916 @@ TEST(NNAPIDelegate, TransposeSimpleTest) {
2, 6, 10, 14, 18, 22, 3, 7, 11, 15, 19, 23}));
}
+class FloatSubOpModel : public SingleOpModelWithNNAPI {
+ public:
+ FloatSubOpModel(const TensorData& input1, const TensorData& input2,
+ const TensorData& output,
+ ActivationFunctionType activation_type) {
+ input1_ = AddInput(input1);
+ input2_ = AddInput(input2);
+ output_ = AddOutput(output);
+ SetBuiltinOp(BuiltinOperator_SUB, BuiltinOptions_SubOptions,
+ CreateMulOptions(builder_, activation_type).Union());
+ BuildInterpreter({GetShape(input1_), GetShape(input2_)});
+ }
+
+ int input1() { return input1_; }
+ int input2() { return input2_; }
+
+ std::vector<float> GetOutput() { return ExtractVector<float>(output_); }
+
+ protected:
+ int input1_;
+ int input2_;
+ int output_;
+};
+
+TEST(NNAPIDelegate, SubWithNoActivation) {
+ FloatSubOpModel m({TensorType_FLOAT32, {1, 2, 2, 1}},
+ {TensorType_FLOAT32, {1, 2, 2, 1}},
+ {TensorType_FLOAT32, {}}, ActivationFunctionType_NONE);
+ m.PopulateTensor<float>(m.input1(), {-2.0, 0.2, 0.7, 0.8});
+ m.PopulateTensor<float>(m.input2(), {0.1, 0.2, 0.3, 0.5});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutput(),
+ ElementsAreArray(ArrayFloatNear({-2.1, 0.0, 0.4, 0.3})));
+}
+
+class FloatDivOpModel : public SingleOpModelWithNNAPI {
+ public:
+ FloatDivOpModel(const TensorData& input1, const TensorData& input2,
+ const TensorData& output,
+ ActivationFunctionType activation_type) {
+ input1_ = AddInput(input1);
+ input2_ = AddInput(input2);
+ output_ = AddOutput(output);
+ SetBuiltinOp(BuiltinOperator_DIV, BuiltinOptions_DivOptions,
+ CreateMulOptions(builder_, activation_type).Union());
+ BuildInterpreter({GetShape(input1_), GetShape(input2_)});
+ }
+
+ int input1() { return input1_; }
+ int input2() { return input2_; }
+
+ std::vector<float> GetOutput() { return ExtractVector<float>(output_); }
+
+ protected:
+ int input1_;
+ int input2_;
+ int output_;
+};
+
+TEST(NNAPIDelegate, DivWithNoActivation) {
+ FloatDivOpModel m({TensorType_FLOAT32, {1, 2, 2, 1}},
+ {TensorType_FLOAT32, {1, 2, 2, 1}},
+ {TensorType_FLOAT32, {}}, ActivationFunctionType_NONE);
+ m.PopulateTensor<float>(m.input1(), {-2.0, 0.2, 0.8, 0.8});
+ m.PopulateTensor<float>(m.input2(), {0.1, 0.2, 0.4, 0.2});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({-20, 1, 2, 4})));
+}
+
+class BaseConcatenationOpModel : public SingleOpModelWithNNAPI {
+ public:
+ BaseConcatenationOpModel() {}
+ BaseConcatenationOpModel(const TensorData& input_template, int axis,
+ int num_inputs) {
+ std::vector<std::vector<int>> all_input_shapes;
+ for (int i = 0; i < num_inputs; ++i) {
+ all_input_shapes.push_back(input_template.shape);
+ AddInput(input_template);
+ }
+ output_ = AddOutput({input_template.type, /*shape=*/{}, input_template.min,
+ input_template.max});
+ SetBuiltinOp(
+ BuiltinOperator_CONCATENATION, BuiltinOptions_ConcatenationOptions,
+ CreateConcatenationOptions(builder_, axis, ActivationFunctionType_NONE)
+ .Union());
+ BuildInterpreter(all_input_shapes);
+ }
+
+ protected:
+ int output_;
+};
+
+class ConcatenationOpModel : public BaseConcatenationOpModel {
+ public:
+ using BaseConcatenationOpModel::BaseConcatenationOpModel;
+ void SetInput(int index, std::initializer_list<float> data) {
+ PopulateTensor(index, data);
+ }
+ std::vector<float> GetOutput() { return ExtractVector<float>(output_); }
+};
+
+TEST(NNAPIDelegate, ConcatenationThreeDimensionalOneInput) {
+ ConcatenationOpModel m0({TensorType_FLOAT32, {2, 1, 2}}, /*axis=*/1,
+ /*num_inputs=*/1);
+ m0.SetInput(0, {1.0f, 3.0f, 4.0f, 7.0f});
+ m0.Invoke();
+ EXPECT_THAT(m0.GetOutput(), ElementsAreArray({1, 3, 4, 7}));
+}
+
+TEST(NNAPIDelegate, ConcatenationFourInputs) {
+ ConcatenationOpModel m0({TensorType_FLOAT32, {2, 1, 2}}, /*axis=*/2,
+ /*num_inputs=*/4);
+ m0.SetInput(0, {1.0f, 3.0f, 4.0f, 7.0f});
+ m0.SetInput(1, {1.1f, 3.1f, 4.1f, 7.1f});
+ m0.SetInput(2, {1.2f, 3.2f, 4.2f, 7.2f});
+ m0.SetInput(3, {1.3f, 3.3f, 4.3f, 7.3f});
+ m0.Invoke();
+ EXPECT_THAT(m0.GetOutput(),
+ ElementsAreArray({
+ 1.0f, 3.0f, 1.1f, 3.1f, 1.2f, 3.2f, 1.3f, 3.3f, //
+ 4.0f, 7.0f, 4.1f, 7.1f, 4.2f, 7.2f, 4.3f, 7.3f, //
+ }));
+}
+
+class QuantizedConcatenationOpModel : public BaseConcatenationOpModel {
+ public:
+ using BaseConcatenationOpModel::BaseConcatenationOpModel;
+ QuantizedConcatenationOpModel(const std::vector<TensorData>& input_template,
+ int axis, int num_inputs,
+ const TensorData& output_template) {
+ std::vector<std::vector<int>> all_input_shapes;
+ CHECK_EQ(input_template.size(), num_inputs);
+ for (int i = 0; i < num_inputs; ++i) {
+ all_input_shapes.push_back(input_template[i].shape);
+ AddInput(input_template[i]);
+ }
+ output_ = AddOutput({output_template.type, /*shape=*/{},
+ output_template.min, output_template.max});
+ SetBuiltinOp(
+ BuiltinOperator_CONCATENATION, BuiltinOptions_ConcatenationOptions,
+ CreateConcatenationOptions(builder_, axis, ActivationFunctionType_NONE)
+ .Union());
+ BuildInterpreter(all_input_shapes);
+ }
+ void SetInput(int index, std::initializer_list<float> data) {
+ QuantizeAndPopulate<uint8_t>(index, data);
+ }
+ std::vector<uint8_t> GetOutput() { return ExtractVector<uint8_t>(output_); }
+ std::vector<float> GetDequantizedOutput() {
+ return Dequantize<uint8_t>(ExtractVector<uint8_t>(output_),
+ GetScale(output_), GetZeroPoint(output_));
+ }
+};
+
+TEST(NNAPIDelegate, ConcatenationFourInputsQuantized) {
+ QuantizedConcatenationOpModel m0({TensorType_UINT8, {2, 1, 2}, -12.7, 12.8},
+ /*axis=*/2,
+ /*num_inputs=*/4);
+
+ m0.SetInput(0, {1.0f, 3.0f, 4.0f, 7.0f});
+ m0.SetInput(1, {1.1f, 3.1f, 4.1f, 7.1f});
+ m0.SetInput(2, {1.2f, 3.2f, 4.2f, 7.2f});
+ m0.SetInput(3, {1.3f, 3.3f, 4.3f, 7.3f});
+ m0.Invoke();
+ EXPECT_THAT(m0.GetDequantizedOutput(),
+ ElementsAreArray(ArrayFloatNear({
+ 1.0f, 3.0f, 1.1f, 3.1f, 1.2f, 3.2f, 1.3f, 3.3f, //
+ 4.0f, 7.0f, 4.1f, 7.1f, 4.2f, 7.2f, 4.3f, 7.3f, //
+ })));
+ EXPECT_THAT(m0.GetOutput(), ElementsAreArray({
+ 137, 157, 138, 158, 139, 159, 140, 160, //
+ 167, 197, 168, 198, 169, 199, 170, 200, //
+ }));
+}
+
+TEST(NNAPIDelegate, ConcatenationFourInputsQuantizedMixedRange) {
+ QuantizedConcatenationOpModel m0({{TensorType_UINT8, {2, 1, 2}, -10.7, 10.8},
+ {TensorType_UINT8, {2, 1, 2}, 0, 12.8},
+ {TensorType_UINT8, {2, 1, 2}, -11, 11.8},
+ {TensorType_UINT8, {2, 1, 2}, 0, 7.4}},
+ /*axis=*/2, /*num_inputs=*/4,
+ {TensorType_UINT8, {2, 1, 2}, -12.7, 12.8});
+
+ m0.SetInput(0, {1.0f, 3.0f, 4.0f, 7.0f});
+ m0.SetInput(1, {1.1f, 3.1f, 4.1f, 7.1f});
+ m0.SetInput(2, {1.2f, 3.2f, 4.2f, 7.2f});
+ m0.SetInput(3, {1.3f, 3.3f, 4.3f, 7.3f});
+ m0.Invoke();
+ EXPECT_THAT(m0.GetDequantizedOutput(),
+ ElementsAreArray(ArrayFloatNear({
+ 1.0f, 3.0f, 1.1f, 3.1f, 1.2f, 3.2f, 1.3f, 3.3f, //
+ 4.0f, 7.0f, 4.1f, 7.1f, 4.2f, 7.2f, 4.3f, 7.3f, //
+ })));
+ EXPECT_THAT(m0.GetOutput(), ElementsAreArray({
+ 137, 157, 138, 158, 139, 159, 140, 160, //
+ 167, 197, 168, 198, 169, 199, 170, 200, //
+ }));
+}
+
+class DequantizeOpModel : public SingleOpModelWithNNAPI {
+ public:
+ DequantizeOpModel(std::initializer_list<int> shape, float min, float max) {
+ input_ = AddInput({TensorType_UINT8, shape, min, max});
+ output_ = AddOutput({TensorType_FLOAT32, shape});
+ SetBuiltinOp(BuiltinOperator_DEQUANTIZE, BuiltinOptions_DequantizeOptions,
+ CreateDequantizeOptions(builder_).Union());
+
+ BuildInterpreter({GetShape(input_)});
+ }
+
+ void SetInput(std::initializer_list<uint8_t> data) {
+ PopulateTensor(input_, data);
+ }
+
+ std::vector<float> GetOutput() { return ExtractVector<float>(output_); }
+
+ private:
+ int input_;
+ int output_;
+};
+
+TEST(NNAPIDelegate, DequantizeFourDimensional) {
+ DequantizeOpModel m({2, 5}, -63.5, 64);
+
+ m.SetInput({0, 1, 2, 3, 4, 251, 252, 253, 254, 255});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutput(),
+ ElementsAreArray(ArrayFloatNear(
+ {-63.5, -63, -62.5, -62, -61.5, 62, 62.5, 63, 63.5, 64})));
+}
+
+class FloorOpModel : public SingleOpModelWithNNAPI {
+ public:
+ FloorOpModel(std::initializer_list<int> input_shape, TensorType input_type) {
+ input_ = AddInput(TensorType_FLOAT32);
+ output_ = AddOutput(TensorType_FLOAT32);
+ SetBuiltinOp(BuiltinOperator_FLOOR, BuiltinOptions_NONE, 0);
+ BuildInterpreter({
+ input_shape,
+ });
+ }
+
+ int input() { return input_; }
+
+ std::vector<float> GetOutput() { return ExtractVector<float>(output_); }
+ std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
+
+ private:
+ int input_;
+ int output_;
+};
+
+TEST(NNAPIDelegate, FloorSingleDim) {
+ FloorOpModel model({2}, TensorType_FLOAT32);
+ model.PopulateTensor<float>(model.input(), {8.5, 0.0});
+ model.Invoke();
+ EXPECT_THAT(model.GetOutput(), ElementsAreArray({8, 0}));
+ EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2}));
+}
+
+TEST(NNAPIDelegate, FloorMultiDims) {
+ FloorOpModel model({2, 1, 1, 5}, TensorType_FLOAT32);
+ model.PopulateTensor<float>(model.input(), {
+ 0.0001,
+ 8.0001,
+ 0.9999,
+ 9.9999,
+ 0.5,
+ -0.0001,
+ -8.0001,
+ -0.9999,
+ -9.9999,
+ -0.5,
+ });
+ model.Invoke();
+ EXPECT_THAT(model.GetOutput(),
+ ElementsAreArray({0, 8, 0, 9, 0, -1, -9, -1, -10, -1}));
+ EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 1, 1, 5}));
+}
+
+class LocalResponseNormOpModel : public SingleOpModelWithNNAPI {
+ public:
+ LocalResponseNormOpModel(std::initializer_list<int> input_shape, int radius,
+ float bias, float alpha, float beta) {
+ input_ = AddInput(TensorType_FLOAT32);
+ output_ = AddOutput(TensorType_FLOAT32);
+ SetBuiltinOp(BuiltinOperator_LOCAL_RESPONSE_NORMALIZATION,
+ BuiltinOptions_LocalResponseNormalizationOptions,
+ CreateLocalResponseNormalizationOptions(builder_, radius, bias,
+ alpha, beta)
+ .Union());
+ BuildInterpreter({input_shape});
+ }
+
+ void SetInput(std::initializer_list<float> data) {
+ PopulateTensor(input_, data);
+ }
+
+ std::vector<float> GetOutput() { return ExtractVector<float>(output_); }
+
+ private:
+ int input_;
+ int output_;
+};
+
+TEST(NNAPIDelegate, LocalResponseNormSameAsL2Norm) {
+ LocalResponseNormOpModel m({1, 1, 1, 6}, /*radius=*/20, /*bias=*/0.0,
+ /*alpha=*/1.0, /*beta=*/0.5);
+ m.SetInput({-1.1, 0.6, 0.7, 1.2, -0.7, 0.1});
+ m.Invoke();
+ // The result is every input divided by 2.
+ EXPECT_THAT(
+ m.GetOutput(),
+ ElementsAreArray(ArrayFloatNear({-0.55, 0.3, 0.35, 0.6, -0.35, 0.05})));
+}
+
+TEST(NNAPIDelegate, LocalResponseNormWithAlpha) {
+ LocalResponseNormOpModel m({1, 1, 1, 6}, /*radius=*/20, /*bias=*/0.0,
+ /*alpha=*/4.0, /*beta=*/0.5);
+ m.SetInput({-1.1, 0.6, 0.7, 1.2, -0.7, 0.1});
+ m.Invoke();
+ // The result is every input divided by 3.
+ EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear(
+ {-0.275, 0.15, 0.175, 0.3, -0.175, 0.025})));
+}
+
+TEST(NNAPIDelegate, LocalResponseNormWithBias) {
+ LocalResponseNormOpModel m({1, 1, 1, 6}, /*radius=*/20, /*bias=*/9.0,
+ /*alpha=*/4.0, /*beta=*/0.5);
+ m.SetInput({-1.1, 0.6, 0.7, 1.2, -0.7, 0.1});
+ m.Invoke();
+ // The result is every input divided by 5.
+ EXPECT_THAT(
+ m.GetOutput(),
+ ElementsAreArray(ArrayFloatNear({-0.22, 0.12, 0.14, 0.24, -0.14, 0.02})));
+}
+
+TEST(NNAPIDelegate, LocalResponseNormSmallRadius) {
+ LocalResponseNormOpModel m({1, 1, 1, 6}, /*radius=*/2, /*bias=*/9.0,
+ /*alpha=*/4.0, /*beta=*/0.5);
+ m.SetInput({-1.1, 0.6, 0.7, 1.2, -0.7, 0.1});
+ m.Invoke();
+ EXPECT_THAT(
+ m.GetOutput(),
+ ElementsAreArray(ArrayFloatNear(
+ {-0.264926, 0.125109, 0.140112, 0.267261, -0.161788, 0.0244266})));
+}
+
+class LSHProjectionOpModel : public SingleOpModelWithNNAPI {
+ public:
+ LSHProjectionOpModel(LSHProjectionType type,
+ std::initializer_list<int> hash_shape,
+ std::initializer_list<int> input_shape,
+ std::initializer_list<int> weight_shape) {
+ hash_ = AddInput(TensorType_FLOAT32);
+ input_ = AddInput(TensorType_INT32);
+ if (weight_shape.size() > 0) {
+ weight_ = AddInput(TensorType_FLOAT32);
+ }
+ output_ = AddOutput(TensorType_INT32);
+
+ SetBuiltinOp(BuiltinOperator_LSH_PROJECTION,
+ BuiltinOptions_LSHProjectionOptions,
+ CreateLSHProjectionOptions(builder_, type).Union());
+ if (weight_shape.size() > 0) {
+ BuildInterpreter({hash_shape, input_shape, weight_shape});
+ } else {
+ BuildInterpreter({hash_shape, input_shape});
+ }
+
+ output_size_ = 1;
+ for (int i : hash_shape) {
+ output_size_ *= i;
+ if (type == LSHProjectionType_SPARSE) {
+ break;
+ }
+ }
+ }
+ void SetInput(std::initializer_list<int> data) {
+ PopulateTensor(input_, data);
+ }
+
+ void SetHash(std::initializer_list<float> data) {
+ PopulateTensor(hash_, data);
+ }
+
+ void SetWeight(std::initializer_list<float> f) { PopulateTensor(weight_, f); }
+
+ std::vector<int> GetOutput() { return ExtractVector<int>(output_); }
+
+ private:
+ int input_;
+ int hash_;
+ int weight_;
+ int output_;
+
+ int output_size_;
+};
+
+TEST(NNAPIDelegate, LSHProjectionDense1DInputs) {
+ LSHProjectionOpModel m(LSHProjectionType_DENSE, {3, 2}, {5}, {5});
+
+ m.SetInput({12345, 54321, 67890, 9876, -12345678});
+ m.SetHash({0.123, 0.456, -0.321, 1.234, 5.678, -4.321});
+ m.SetWeight({1.0, 1.0, 1.0, 1.0, 1.0});
+
+ m.Invoke();
+
+ EXPECT_THAT(m.GetOutput(), ElementsAre(0, 0, 0, 1, 0, 0));
+}
+
+TEST(NNAPIDelegate, LSHProjectionSparse1DInputs) {
+ LSHProjectionOpModel m(LSHProjectionType_SPARSE, {3, 2}, {5}, {});
+
+ m.SetInput({12345, 54321, 67890, 9876, -12345678});
+ m.SetHash({0.123, 0.456, -0.321, 1.234, 5.678, -4.321});
+
+ m.Invoke();
+
+ EXPECT_THAT(m.GetOutput(), ElementsAre(0 + 0, 4 + 1, 8 + 0));
+}
+
+TEST(NNAPIDelegate, LSHProjectionSparse3DInputs) {
+ LSHProjectionOpModel m(LSHProjectionType_SPARSE, {3, 2}, {5, 2, 2}, {5});
+
+ m.SetInput({1234, 2345, 3456, 1234, 4567, 5678, 6789, 4567, 7891, 8912,
+ 9123, 7890, -987, -876, -765, -987, -543, -432, -321, -543});
+ m.SetHash({0.123, 0.456, -0.321, 1.234, 5.678, -4.321});
+ m.SetWeight({0.12, 0.34, 0.56, 0.67, 0.78});
+
+ m.Invoke();
+
+ EXPECT_THAT(m.GetOutput(), ElementsAre(0 + 2, 4 + 1, 8 + 1));
+}
+
+class BaseActivationsOpModel : public SingleOpModelWithNNAPI {
+ public:
+ // Most activations don't take any options, so this constructor works for
+ // them.
+ BaseActivationsOpModel(BuiltinOperator type, TensorData input) {
+ input_ = AddInput(input);
+ if (input.type == TensorType_UINT8) {
+ output_ = AddOutput({input.type, {}, 0, 0, 1. / 256});
+ } else {
+ output_ = AddOutput({input.type, {}});
+ }
+ SetBuiltinOp(type, BuiltinOptions_NONE, 0);
+ BuildInterpreter({GetShape(input_)});
+ }
+
+ BaseActivationsOpModel(BuiltinOperator type, const TensorData& input,
+ const TensorData& output) {
+ input_ = AddInput(input);
+ output_ = AddOutput(output);
+ SetBuiltinOp(type, BuiltinOptions_NONE, 0);
+ BuildInterpreter({GetShape(input_)});
+ }
+
+ protected:
+ int input_;
+ int output_;
+};
+
+class FloatActivationsOpModel : public BaseActivationsOpModel {
+ public:
+ using BaseActivationsOpModel::BaseActivationsOpModel;
+
+ void SetInput(std::initializer_list<float> data) {
+ PopulateTensor(input_, data);
+ }
+ std::vector<float> GetOutput() { return ExtractVector<float>(output_); }
+};
+
+const float kQuantizedTolerance = 2 * (1. / 256);
+
+class QuantizedActivationsOpModel : public BaseActivationsOpModel {
+ public:
+ using BaseActivationsOpModel::BaseActivationsOpModel;
+
+ template <typename T>
+ void SetInput(std::initializer_list<float> data) {
+ QuantizeAndPopulate<T>(input_, data);
+ }
+ template <typename T>
+
+ std::vector<T> GetOutput() {
+ return ExtractVector<T>(output_);
+ }
+ template <typename T>
+ std::vector<float> GetDequantizedOutput() {
+ return Dequantize<T>(ExtractVector<T>(output_), GetScale(output_),
+ GetZeroPoint(output_));
+ }
+};
+
+TEST(NNAPIDelegate, Relu) {
+ FloatActivationsOpModel m(BuiltinOperator_RELU,
+ /*input=*/{TensorType_FLOAT32, {1, 2, 4, 1}});
+ m.SetInput({
+ 0, -6, 2, 4, //
+ 3, -2, 10, 1, //
+ });
+ m.Invoke();
+ EXPECT_THAT(m.GetOutput(), ElementsAreArray({
+ 0, 0, 2, 4, //
+ 3, 0, 10, 1, //
+ }));
+}
+
+TEST(NNAPIDelegate, Relu1) {
+ FloatActivationsOpModel m(BuiltinOperator_RELU_N1_TO_1,
+ /*input=*/{TensorType_FLOAT32, {1, 2, 4, 1}});
+ m.SetInput({
+ 0.0, -0.6, 0.2, -0.4, //
+ 0.3, -2.0, 1.1, -0.1, //
+ });
+ m.Invoke();
+ EXPECT_THAT(m.GetOutput(), ElementsAreArray({
+ 0.0, -0.6, 0.2, -0.4, //
+ 0.3, -1.0, 1.0, -0.1, //
+ }));
+}
+
+TEST(NNAPIDelegate, Relu6) {
+ FloatActivationsOpModel m(BuiltinOperator_RELU6,
+ /*input=*/{TensorType_FLOAT32, {1, 2, 4, 1}});
+ m.SetInput({
+ 0, -6, 2, 4, //
+ 3, -2, 10, 1, //
+ });
+ m.Invoke();
+ EXPECT_THAT(m.GetOutput(), ElementsAreArray({
+ 0, 0, 2, 4, //
+ 3, 0, 6, 1, //
+ }));
+}
+
+TEST(NNAPIDelegate, Tanh) {
+ FloatActivationsOpModel m(BuiltinOperator_TANH,
+ /*input=*/{TensorType_FLOAT32, {1, 2, 4, 1}});
+ m.SetInput({
+ 0, -6, 2, 4, //
+ 3, -2, 10, 1, //
+ });
+ m.Invoke();
+ EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({
+ 0, -0.9999877, 0.9640275, 0.999329, //
+ 0.99505475, -0.9640275, 1, 0.7615941, //
+ })));
+}
+
+TEST(NNAPIDelegate, LogisticFloat) {
+ FloatActivationsOpModel m(BuiltinOperator_LOGISTIC,
+ /*input=*/{TensorType_FLOAT32, {1, 2, 4, 1}});
+ m.SetInput({
+ 0, -6, 2, 4, //
+ 3, -2, 10, 1, //
+ });
+ m.Invoke();
+ EXPECT_THAT(m.GetOutput(), ElementsAreArray(ArrayFloatNear({
+ 0.5, 0.002473, 0.880797, 0.982014, //
+ 0.952574, 0.119203, 0.999955, 0.731059, //
+ })));
+}
+
+TEST(NNAPIDelegate, LogisticQuantized) {
+ QuantizedActivationsOpModel m(
+ BuiltinOperator_LOGISTIC,
+ /*input=*/{TensorType_UINT8, {1, 2, 4, 1}, -10, 10});
+ m.SetInput<uint8_t>({
+ 0, -6, 2, 4, //
+ 3, -2, 10, 1, //
+ });
+ m.Invoke();
+ EXPECT_THAT(m.GetDequantizedOutput<uint8_t>(),
+ ElementsAreArray(ArrayFloatNear(
+ {
+ 0.5, 0.002473, 0.880797, 0.982014, //
+ 0.952574, 0.119203, 0.999955, 0.731059, //
+ },
+ kQuantizedTolerance)));
+ EXPECT_THAT(m.GetOutput<uint8_t>(),
+ ElementsAreArray({128, 1, 227, 251, 244, 32, 255, 188}));
+}
+
+#if 0
+class ResizeBilinearOpModel : public SingleOpModelWithNNAPI {
+ public:
+ ResizeBilinearOpModel(const TensorData& input,
+ std::initializer_list<int> size_data = {}) {
+ bool const_size = size_data.size() != 0;
+ input_ = AddInput(input);
+ if (const_size) {
+ size_ = AddConstInput(TensorType_INT32, size_data, {2});
+ } else {
+ size_ = AddInput({TensorType_INT32, {2}});
+ }
+ output_ = AddOutput(input.type);
+ SetBuiltinOp(BuiltinOperator_RESIZE_BILINEAR,
+ BuiltinOptions_ResizeBilinearOptions,
+ CreateResizeBilinearOptions(builder_).Union());
+ if (const_size) {
+ BuildInterpreter({GetShape(input_)});
+ } else {
+ BuildInterpreter({GetShape(input_), GetShape(size_)});
+ }
+ }
+
+ template <typename T>
+ void SetInput(std::initializer_list<T> data) {
+ PopulateTensor(input_, data);
+ }
+ void SetSize(std::initializer_list<int> data) { PopulateTensor(size_, data); }
+
+ template <typename T>
+ std::vector<T> GetOutput() {
+ return ExtractVector<T>(output_);
+ }
+
+ private:
+ int input_;
+ int size_;
+ int output_;
+};
+
+TEST(NNAPIDelegate, ResizeBilinearHorizontal) {
+ ResizeBilinearOpModel m({TensorType_FLOAT32, {1, 1, 2, 1}});
+ m.SetInput<float>({3, 6});
+ m.SetSize({1, 3});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutput<float>(),
+ ElementsAreArray(ArrayFloatNear({3, 5, 6})));
+
+ ResizeBilinearOpModel const_m({TensorType_FLOAT32, {1, 1, 2, 1}}, {1, 3});
+ const_m.SetInput<float>({3, 6});
+ const_m.Invoke();
+ EXPECT_THAT(const_m.GetOutput<float>(),
+ ElementsAreArray(ArrayFloatNear({3, 5, 6})));
+}
+
+TEST(NNAPIDelegate, ResizeBilinearVertical) {
+ ResizeBilinearOpModel m({TensorType_FLOAT32, {1, 2, 1, 1}});
+ m.SetInput<float>({3, 9});
+ m.SetSize({3, 1});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutput<float>(),
+ ElementsAreArray(ArrayFloatNear({3, 7, 9})));
+
+ ResizeBilinearOpModel const_m({TensorType_FLOAT32, {1, 2, 1, 1}}, {3, 1});
+ const_m.SetInput<float>({3, 9});
+ const_m.Invoke();
+ EXPECT_THAT(const_m.GetOutput<float>(),
+ ElementsAreArray(ArrayFloatNear({3, 7, 9})));
+}
+
+TEST(NNAPIDelegate, ResizeBilinearTwoDimensional) {
+ ResizeBilinearOpModel m({TensorType_FLOAT32, {1, 2, 2, 1}});
+ m.SetInput<float>({
+ 3, 6, //
+ 9, 12 //
+ });
+ m.SetSize({3, 3});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutput<float>(), ElementsAreArray(ArrayFloatNear({
+ 3, 5, 6, //
+ 7, 9, 10, //
+ 9, 11, 12, //
+ })));
+
+ ResizeBilinearOpModel const_m({TensorType_FLOAT32, {1, 2, 2, 1}}, {3, 3});
+ const_m.SetInput<float>({
+ 3, 6, //
+ 9, 12 //
+ });
+ const_m.Invoke();
+ EXPECT_THAT(const_m.GetOutput<float>(), ElementsAreArray(ArrayFloatNear({
+ 3, 5, 6, //
+ 7, 9, 10, //
+ 9, 11, 12, //
+ })));
+}
+#endif
+
+template <typename T>
+class PadOpModel : public SingleOpModelWithNNAPI {
+ public:
+ void SetInput(std::initializer_list<T> data) {
+ PopulateTensor<T>(input_, data);
+ }
+
+ void SetQuantizedInput(std::initializer_list<float> data) {
+ QuantizeAndPopulate<uint8_t>(input_, data);
+ }
+
+ void SetQuantizedPadValue(float data) {
+ QuantizeAndPopulate<uint8_t>(constant_values_, {data});
+ }
+
+ void SetPaddings(std::initializer_list<int> paddings) {
+ PopulateTensor<int>(paddings_, paddings);
+ }
+
+ std::vector<T> GetOutput() { return ExtractVector<T>(output_); }
+ std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
+
+ std::vector<float> GetDequantizedOutput() {
+ return Dequantize<uint8_t>(ExtractVector<uint8_t>(output_),
+ GetScale(output_), GetZeroPoint(output_));
+ }
+
+ protected:
+ int input_;
+ int output_;
+ int paddings_;
+ int constant_values_;
+};
+
+class PadOpConstModel : public PadOpModel<float> {
+ public:
+ PadOpConstModel(const TensorData& input,
+ std::initializer_list<int> paddings_shape,
+ std::initializer_list<int> paddings,
+ const TensorData& output) {
+ input_ = AddInput(input);
+ paddings_ = AddConstInput(TensorType_INT32, paddings, paddings_shape);
+ output_ = AddOutput(output);
+
+ SetBuiltinOp(BuiltinOperator_PAD, BuiltinOptions_PadOptions,
+ CreatePadOptions(builder_).Union());
+ BuildInterpreter({input.shape});
+ }
+};
+
+TEST(NNAPIDelegate, PadAdvancedConstTest) {
+ PadOpConstModel m({TensorType_FLOAT32, {1, 2, 3, 1}}, {4, 2},
+ {0, 0, 0, 2, 1, 3, 0, 0}, {TensorType_FLOAT32});
+ m.SetInput({1, 2, 3, 4, 5, 6});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutput(),
+ ElementsAreArray({0, 1, 2, 3, 0, 0, 0, 0, 4, 5, 6, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}));
+ EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 4, 7, 1}));
+}
+
+class SpaceToBatchNDOpModel : public SingleOpModelWithNNAPI {
+ public:
+ void SetInput(std::initializer_list<float> data) {
+ PopulateTensor<float>(input_, data);
+ }
+
+ void SetBlockShape(std::initializer_list<int> data) {
+ PopulateTensor<int>(block_shape_, data);
+ }
+
+ void SetPaddings(std::initializer_list<int> data) {
+ PopulateTensor<int>(paddings_, data);
+ }
+
+ std::vector<float> GetOutput() { return ExtractVector<float>(output_); }
+ std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
+
+ protected:
+ int input_;
+ int block_shape_;
+ int paddings_;
+ int output_;
+};
+
+class SpaceToBatchNDOpConstModel : public SpaceToBatchNDOpModel {
+ public:
+ SpaceToBatchNDOpConstModel(std::initializer_list<int> input_shape,
+ std::initializer_list<int> block_shape,
+ std::initializer_list<int> paddings) {
+ input_ = AddInput(TensorType_FLOAT32);
+ block_shape_ = AddConstInput(TensorType_INT32, block_shape, {2});
+ paddings_ = AddConstInput(TensorType_INT32, paddings, {2, 2});
+ output_ = AddOutput(TensorType_FLOAT32);
+
+ SetBuiltinOp(BuiltinOperator_SPACE_TO_BATCH_ND,
+ BuiltinOptions_SpaceToBatchNDOptions,
+ CreateSpaceToBatchNDOptions(builder_).Union());
+ BuildInterpreter({input_shape});
+ }
+};
+
+TEST(NNAPIDelegate, SpaceToBatchNDSimpleConstTest) {
+ SpaceToBatchNDOpConstModel m({1, 4, 4, 1}, {2, 2}, {0, 0, 0, 0});
+ m.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({4, 2, 2, 1}));
+ EXPECT_THAT(m.GetOutput(), ElementsAreArray({1, 3, 9, 11, 2, 4, 10, 12, 5, 7,
+ 13, 15, 6, 8, 14, 16}));
+}
+
+TEST(NNAPIDelegate, SpaceToBatchNDMultipleInputBatchesConstTest) {
+ SpaceToBatchNDOpConstModel m({2, 2, 4, 1}, {2, 2}, {0, 0, 0, 0});
+ m.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({8, 1, 2, 1}));
+ EXPECT_THAT(m.GetOutput(), ElementsAreArray({1, 3, 9, 11, 2, 4, 10, 12, 5, 7,
+ 13, 15, 6, 8, 14, 16}));
+}
+
+TEST(NNAPIDelegate, SpaceToBatchNDSimplePaddingConstTest) {
+ SpaceToBatchNDOpConstModel m({1, 5, 2, 1}, {3, 2}, {1, 0, 2, 0});
+ m.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({6, 2, 2, 1}));
+ EXPECT_THAT(m.GetOutput(), ElementsAreArray({
+ 0, 0, 0, 5, 0, 0, 0, 6, 0, 1, 0, 7,
+ 0, 2, 0, 8, 0, 3, 0, 9, 0, 4, 0, 10,
+ }));
+}
+
+TEST(NNAPIDelegate, SpaceToBatchNDComplexPaddingConstTest) {
+ SpaceToBatchNDOpConstModel m({1, 4, 2, 1}, {3, 2}, {1, 1, 2, 4});
+ m.SetInput({1, 2, 3, 4, 5, 6, 7, 8});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({6, 2, 4, 1}));
+ EXPECT_THAT(m.GetOutput(), ElementsAreArray({
+ 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0,
+ 0, 1, 0, 0, 0, 7, 0, 0, 0, 2, 0, 0, 0, 8, 0, 0,
+ 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0,
+ }));
+}
+
+template <typename input_type = float,
+ TensorType tensor_input_type = TensorType_FLOAT32>
+class StridedSliceOpModel : public SingleOpModelWithNNAPI {
+ public:
+ StridedSliceOpModel(std::initializer_list<int> input_shape,
+ std::initializer_list<int> begin_shape,
+ std::initializer_list<int> end_shape,
+ std::initializer_list<int> strides_shape, int begin_mask,
+ int end_mask, int ellipsis_mask, int new_axis_mask,
+ int shrink_axis_mask) {
+ input_ = AddInput(tensor_input_type);
+ begin_ = AddInput(TensorType_INT32);
+ end_ = AddInput(TensorType_INT32);
+ strides_ = AddInput(TensorType_INT32);
+ output_ = AddOutput(tensor_input_type);
+ SetBuiltinOp(
+ BuiltinOperator_STRIDED_SLICE, BuiltinOptions_StridedSliceOptions,
+ CreateStridedSliceOptions(builder_, begin_mask, end_mask, ellipsis_mask,
+ new_axis_mask, shrink_axis_mask)
+ .Union());
+ BuildInterpreter({input_shape, begin_shape, end_shape, strides_shape});
+ }
+
+ void SetInput(std::initializer_list<input_type> data) {
+ PopulateTensor<input_type>(input_, data);
+ }
+ void SetBegin(std::initializer_list<int32_t> data) {
+ PopulateTensor<int32_t>(begin_, data);
+ }
+ void SetEnd(std::initializer_list<int32_t> data) {
+ PopulateTensor<int32_t>(end_, data);
+ }
+ void SetStrides(std::initializer_list<int32_t> data) {
+ PopulateTensor<int32_t>(strides_, data);
+ }
+
+ std::vector<input_type> GetOutput() {
+ return ExtractVector<input_type>(output_);
+ }
+ std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
+
+ private:
+ int input_;
+ int begin_;
+ int end_;
+ int strides_;
+ int output_;
+};
+
+TEST(NNAPIDelegate, StridedSliceIn2D) {
+ StridedSliceOpModel<> m({2, 3}, {2}, {2}, {2}, 0, 0, 0, 0, 0);
+ m.SetInput({1, 2, 3, 4, 5, 6});
+ m.SetBegin({1, 0});
+ m.SetEnd({2, 2});
+ m.SetStrides({1, 1});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({1, 2}));
+ EXPECT_THAT(m.GetOutput(), ElementsAreArray({4, 5}));
+}
+
+TEST(NNAPIDelegate, StridedSliceIn2D_ShrinkAxis_NegativeSlice) {
+ // This is equivalent to tf.range(4)[:, tf.newaxis][-2, -1].
+ StridedSliceOpModel<> m({4, 1}, {2}, {2}, {2}, 0, 0, 0, 0, 3);
+ m.SetInput({0, 1, 2, 3});
+ m.SetBegin({-2, -1});
+ m.SetEnd({-1, 0});
+ m.SetStrides({1, 1});
+
+ m.Invoke();
+ EXPECT_TRUE(m.GetOutputShape().empty());
+ EXPECT_THAT(m.GetOutput(), ElementsAreArray({2}));
+}
+
+TEST(NNAPIDelegate, StridedSliceIn2D_ShrinkAxisMask) {
+ StridedSliceOpModel<> m({2, 3}, {2}, {2}, {2}, 0, 0, 0, 0, 3);
+ m.SetInput({1, 2, 3, 4, 5, 6});
+ m.SetBegin({0, 0});
+ m.SetEnd({1, 1});
+ m.SetStrides({1, 1});
+ m.Invoke();
+ EXPECT_TRUE(m.GetOutputShape().empty());
+ EXPECT_THAT(m.GetOutput(), ElementsAreArray({1}));
+}
+
} // namespace
} // namespace tflite
diff --git a/tensorflow/contrib/lite/experimental/c/BUILD b/tensorflow/contrib/lite/experimental/c/BUILD
index b09bb9ea10..50f8da66d0 100644
--- a/tensorflow/contrib/lite/experimental/c/BUILD
+++ b/tensorflow/contrib/lite/experimental/c/BUILD
@@ -5,6 +5,7 @@ licenses(["notice"]) # Apache 2.0
load(
"//tensorflow/contrib/lite:build_def.bzl",
"tflite_cc_shared_object",
+ "tflite_copts",
"tflite_jni_binary",
)
@@ -30,16 +31,11 @@ tflite_cc_shared_object(
],
)
-tflite_jni_binary(
- name = "libtensorflowlite_c_jni.so",
- linkscript = ":version_script.lds",
- deps = [":c_api"],
-)
-
cc_library(
name = "c_api",
srcs = ["c_api.cc"],
hdrs = ["c_api.h"],
+ copts = tflite_copts(),
deps = [
"//tensorflow/contrib/lite:context",
"//tensorflow/contrib/lite:framework",
diff --git a/tensorflow/contrib/lite/experimental/c/c_api.cc b/tensorflow/contrib/lite/experimental/c/c_api.cc
index add4c6813d..9d29e8b3e0 100644
--- a/tensorflow/contrib/lite/experimental/c/c_api.cc
+++ b/tensorflow/contrib/lite/experimental/c/c_api.cc
@@ -27,6 +27,8 @@ struct _TFL_Interpreter {
std::unique_ptr<tflite::Interpreter> impl;
};
+// LINT.IfChange
+
TFL_Interpreter* TFL_NewInterpreter(const void* model_data,
int32_t model_size) {
auto model = tflite::FlatBufferModel::BuildFromBuffer(
@@ -113,6 +115,8 @@ TFL_Status TFL_TensorCopyToBuffer(const TFL_Tensor* tensor, void* output_data,
return kTfLiteOk;
}
+// LINT.ThenChange(//tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/SDK/Scripts/Interpreter.cs)
+
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/.gitignore b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/.gitignore
new file mode 100644
index 0000000000..c72a5cae9e
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/.gitignore
@@ -0,0 +1,13 @@
+# Unity generated
+Builds/
+Temp/
+Library/
+obj/
+# Visual Studio / MonoDevelop generated
+*.csproj
+*.unityproj
+*.sln
+*.suo
+*.userprefs
+# OS generated
+.DS_Store
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite.meta b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite.meta
new file mode 100644
index 0000000000..ed9337b53e
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 71d1b4219b1da4aeaa1cebbec324fc81
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples.meta b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples.meta
new file mode 100644
index 0000000000..edcce00939
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d948aead14abd4c88947c9886d16f774
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite.meta b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite.meta
new file mode 100644
index 0000000000..36b35516f0
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b810b85b794fa48fd93100acf5525e1f
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes.meta b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes.meta
new file mode 100644
index 0000000000..d4133da49a
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 154f4201e2e454d4696fa5834eaa3ad3
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes/HelloTFLite.unity b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes/HelloTFLite.unity
new file mode 100644
index 0000000000..9397d8f27a
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes/HelloTFLite.unity
@@ -0,0 +1,242 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!29 &1
+OcclusionCullingSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_OcclusionBakeSettings:
+ smallestOccluder: 5
+ smallestHole: 0.25
+ backfaceThreshold: 100
+ m_SceneGUID: 00000000000000000000000000000000
+ m_OcclusionCullingData: {fileID: 0}
+--- !u!104 &2
+RenderSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 8
+ m_Fog: 0
+ m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
+ m_FogMode: 3
+ m_FogDensity: 0.01
+ m_LinearFogStart: 0
+ m_LinearFogEnd: 300
+ m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
+ m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
+ m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
+ m_AmbientIntensity: 1
+ m_AmbientMode: 3
+ m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
+ m_SkyboxMaterial: {fileID: 0}
+ m_HaloStrength: 0.5
+ m_FlareStrength: 1
+ m_FlareFadeSpeed: 3
+ m_HaloTexture: {fileID: 0}
+ m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
+ m_DefaultReflectionMode: 0
+ m_DefaultReflectionResolution: 128
+ m_ReflectionBounces: 1
+ m_ReflectionIntensity: 1
+ m_CustomReflection: {fileID: 0}
+ m_Sun: {fileID: 0}
+ m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
+--- !u!157 &3
+LightmapSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 11
+ m_GIWorkflowMode: 1
+ m_GISettings:
+ serializedVersion: 2
+ m_BounceScale: 1
+ m_IndirectOutputScale: 1
+ m_AlbedoBoost: 1
+ m_TemporalCoherenceThreshold: 1
+ m_EnvironmentLightingMode: 0
+ m_EnableBakedLightmaps: 0
+ m_EnableRealtimeLightmaps: 0
+ m_LightmapEditorSettings:
+ serializedVersion: 9
+ m_Resolution: 2
+ m_BakeResolution: 40
+ m_TextureWidth: 1024
+ m_TextureHeight: 1024
+ m_AO: 0
+ m_AOMaxDistance: 1
+ m_CompAOExponent: 1
+ m_CompAOExponentDirect: 0
+ m_Padding: 2
+ m_LightmapParameters: {fileID: 0}
+ m_LightmapsBakeMode: 1
+ m_TextureCompression: 1
+ m_FinalGather: 0
+ m_FinalGatherFiltering: 1
+ m_FinalGatherRayCount: 256
+ m_ReflectionCompression: 2
+ m_MixedBakeMode: 2
+ m_BakeBackend: 0
+ m_PVRSampling: 1
+ m_PVRDirectSampleCount: 32
+ m_PVRSampleCount: 500
+ m_PVRBounces: 2
+ m_PVRFilterTypeDirect: 0
+ m_PVRFilterTypeIndirect: 0
+ m_PVRFilterTypeAO: 0
+ m_PVRFilteringMode: 1
+ m_PVRCulling: 1
+ m_PVRFilteringGaussRadiusDirect: 1
+ m_PVRFilteringGaussRadiusIndirect: 5
+ m_PVRFilteringGaussRadiusAO: 2
+ m_PVRFilteringAtrousPositionSigmaDirect: 0.5
+ m_PVRFilteringAtrousPositionSigmaIndirect: 2
+ m_PVRFilteringAtrousPositionSigmaAO: 1
+ m_ShowResolutionOverlay: 1
+ m_LightingDataAsset: {fileID: 0}
+ m_UseShadowmask: 1
+--- !u!196 &4
+NavMeshSettings:
+ serializedVersion: 2
+ m_ObjectHideFlags: 0
+ m_BuildSettings:
+ serializedVersion: 2
+ agentTypeID: 0
+ agentRadius: 0.5
+ agentHeight: 2
+ agentSlope: 45
+ agentClimb: 0.4
+ ledgeDropHeight: 0
+ maxJumpAcrossDistance: 0
+ minRegionArea: 2
+ manualCellSize: 0
+ cellSize: 0.16666667
+ manualTileSize: 0
+ tileSize: 256
+ accuratePlacement: 0
+ debug:
+ m_Flags: 0
+ m_NavMeshData: {fileID: 0}
+--- !u!1 &492081941
+GameObject:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ serializedVersion: 5
+ m_Component:
+ - component: {fileID: 492081945}
+ - component: {fileID: 492081944}
+ - component: {fileID: 492081943}
+ - component: {fileID: 492081942}
+ m_Layer: 0
+ m_Name: Main Camera
+ m_TagString: MainCamera
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!81 &492081942
+AudioListener:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ m_GameObject: {fileID: 492081941}
+ m_Enabled: 1
+--- !u!124 &492081943
+Behaviour:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ m_GameObject: {fileID: 492081941}
+ m_Enabled: 1
+--- !u!20 &492081944
+Camera:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ m_GameObject: {fileID: 492081941}
+ m_Enabled: 1
+ serializedVersion: 2
+ m_ClearFlags: 1
+ m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
+ m_NormalizedViewPortRect:
+ serializedVersion: 2
+ x: 0
+ y: 0
+ width: 1
+ height: 1
+ near clip plane: 0.3
+ far clip plane: 1000
+ field of view: 60
+ orthographic: 1
+ orthographic size: 5
+ m_Depth: -1
+ m_CullingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_RenderingPath: -1
+ m_TargetTexture: {fileID: 0}
+ m_TargetDisplay: 0
+ m_TargetEye: 3
+ m_HDR: 1
+ m_AllowMSAA: 1
+ m_AllowDynamicResolution: 0
+ m_ForceIntoRT: 0
+ m_OcclusionCulling: 1
+ m_StereoConvergence: 10
+ m_StereoSeparation: 0.022
+--- !u!4 &492081945
+Transform:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ m_GameObject: {fileID: 492081941}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: -10}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children:
+ - {fileID: 904015944}
+ m_Father: {fileID: 0}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &904015943
+GameObject:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ serializedVersion: 5
+ m_Component:
+ - component: {fileID: 904015944}
+ - component: {fileID: 904015945}
+ m_Layer: 0
+ m_Name: HelloTFLite
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &904015944
+Transform:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ m_GameObject: {fileID: 904015943}
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_Children: []
+ m_Father: {fileID: 492081945}
+ m_RootOrder: 0
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &904015945
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_PrefabParentObject: {fileID: 0}
+ m_PrefabInternal: {fileID: 0}
+ m_GameObject: {fileID: 904015943}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 899510441e0ca4be0879d3055e467878, type: 3}
+ m_Name:
+ m_EditorClassIdentifier:
+ model: {fileID: 4900000, guid: adff4e1dbdba344c199ee4fe7e84457e, type: 3}
+ inputs:
+ - 1
+ - 3
+ - 7
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes/HelloTFLite.unity.meta b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes/HelloTFLite.unity.meta
new file mode 100644
index 0000000000..e1e13efb66
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes/HelloTFLite.unity.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f8a8c37a396584bb7b21687f33d6d3f8
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes/add.bytes b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes/add.bytes
new file mode 100644
index 0000000000..aef0fe3d82
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes/add.bytes
Binary files differ
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes/add.bytes.meta b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes/add.bytes.meta
new file mode 100644
index 0000000000..ba24871413
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scenes/add.bytes.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: adff4e1dbdba344c199ee4fe7e84457e
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scripts.meta b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scripts.meta
new file mode 100644
index 0000000000..28fde68b8b
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scripts.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f7d1e2dec09b64acdb7b8f5aef9fcb44
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scripts/HelloTFLite.cs b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scripts/HelloTFLite.cs
new file mode 100644
index 0000000000..abca814499
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scripts/HelloTFLite.cs
@@ -0,0 +1,70 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using TensorFlowLite;
+using UnityEngine;
+
+/// <summary>
+/// Simple example demonstrating use of the experimental C# bindings for TensorFlowLite.
+/// </summary>
+public class HelloTFLite : MonoBehaviour {
+
+ [Tooltip("Configurable TFLite model.")]
+ public TextAsset model;
+
+ [Tooltip("Configurable TFLite input tensor data.")]
+ public float[] inputs;
+
+ private Interpreter interpreter;
+ private float[] outputs;
+
+ void Start () {
+ interpreter = new Interpreter(model.bytes);
+ Debug.LogFormat("InputCount: {0}, OutputCount: {1}",
+ interpreter.GetInputTensorCount(),
+ interpreter.GetOutputTensorCount());
+ }
+
+ void Update () {
+ if (inputs == null) {
+ return;
+ }
+
+ if (outputs == null || outputs.Length != inputs.Length) {
+ interpreter.ResizeInputTensor(0, new int[]{inputs.Length});
+ interpreter.AllocateTensors();
+ outputs = new float[inputs.Length];
+ }
+
+ interpreter.SetInputTensorData(0, inputs);
+ interpreter.Invoke();
+ interpreter.GetOutputTensorData(0, outputs);
+
+ Debug.LogFormat("Input: {0}, Output: {1}",
+ ArrayToString(inputs),
+ ArrayToString(outputs));
+ }
+
+ void OnDestroy() {
+ interpreter.Dispose();
+ }
+
+ private static string ArrayToString(float[] values) {
+ return string.Join(",", values.Select(x => x.ToString()).ToArray());
+ }
+}
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scripts/HelloTFLite.cs.meta b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scripts/HelloTFLite.cs.meta
new file mode 100644
index 0000000000..ba83f45084
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/Examples/HelloTFLite/Scripts/HelloTFLite.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 899510441e0ca4be0879d3055e467878
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/SDK.meta b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/SDK.meta
new file mode 100644
index 0000000000..bf5ce15c6a
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/SDK.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 16dad1655bcdc48f7b325a2a634b9c69
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/SDK/Scripts.meta b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/SDK/Scripts.meta
new file mode 100644
index 0000000000..22ed2c466b
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/SDK/Scripts.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d70863368f8904d509a9b73d3a555914
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/SDK/Scripts/Interpreter.cs b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/SDK/Scripts/Interpreter.cs
new file mode 100644
index 0000000000..ab966bae2e
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/SDK/Scripts/Interpreter.cs
@@ -0,0 +1,145 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+using System;
+using System.Runtime.InteropServices;
+
+using TFL_Interpreter = System.IntPtr;
+using TFL_Tensor = System.IntPtr;
+
+namespace TensorFlowLite
+{
+ /// <summary>
+ /// Simple C# bindings for the experimental TensorFlowLite C API.
+ /// </summary>
+ public class Interpreter : IDisposable
+ {
+ private const string TensorFlowLibrary = "tensorflowlite_c";
+
+ private TFL_Interpreter handle;
+
+ public Interpreter(byte[] modelData) {
+ GCHandle modelDataHandle = GCHandle.Alloc(modelData, GCHandleType.Pinned);
+ IntPtr modelDataPtr = modelDataHandle.AddrOfPinnedObject();
+ handle = TFL_NewInterpreter(modelDataPtr, modelData.Length);
+ if (handle == IntPtr.Zero) throw new Exception("Failed to create TensorFlowLite Interpreter");
+ }
+
+ ~Interpreter() {
+ Dispose();
+ }
+
+ public void Dispose() {
+ if (handle != IntPtr.Zero) TFL_DeleteInterpreter(handle);
+ handle = IntPtr.Zero;
+ }
+
+ public void Invoke() {
+ ThrowIfError(TFL_InterpreterInvoke(handle));
+ }
+
+ public int GetInputTensorCount() {
+ return TFL_InterpreterGetInputTensorCount(handle);
+ }
+
+ public void SetInputTensorData(int inputTensorIndex, Array inputTensorData) {
+ GCHandle tensorDataHandle = GCHandle.Alloc(inputTensorData, GCHandleType.Pinned);
+ IntPtr tensorDataPtr = tensorDataHandle.AddrOfPinnedObject();
+ TFL_Tensor tensor = TFL_InterpreterGetInputTensor(handle, inputTensorIndex);
+ ThrowIfError(TFL_TensorCopyFromBuffer(
+ tensor, tensorDataPtr, Buffer.ByteLength(inputTensorData)));
+ }
+
+ public void ResizeInputTensor(int inputTensorIndex, int[] inputTensorShape) {
+ ThrowIfError(TFL_InterpreterResizeInputTensor(
+ handle, inputTensorIndex, inputTensorShape, inputTensorShape.Length));
+ }
+
+ public void AllocateTensors() {
+ ThrowIfError(TFL_InterpreterAllocateTensors(handle));
+ }
+
+ public int GetOutputTensorCount() {
+ return TFL_InterpreterGetOutputTensorCount(handle);
+ }
+
+ public void GetOutputTensorData(int outputTensorIndex, Array outputTensorData) {
+ GCHandle tensorDataHandle = GCHandle.Alloc(outputTensorData, GCHandleType.Pinned);
+ IntPtr tensorDataPtr = tensorDataHandle.AddrOfPinnedObject();
+ TFL_Tensor tensor = TFL_InterpreterGetOutputTensor(handle, outputTensorIndex);
+ ThrowIfError(TFL_TensorCopyToBuffer(
+ tensor, tensorDataPtr, Buffer.ByteLength(outputTensorData)));
+ }
+
+ private static void ThrowIfError(int resultCode) {
+ if (resultCode != 0) throw new Exception("TensorFlowLite operation failed.");
+ }
+
+ #region Externs
+
+ [DllImport (TensorFlowLibrary)]
+ private static extern unsafe TFL_Interpreter TFL_NewInterpreter(
+ IntPtr model_data,
+ int model_size);
+
+ [DllImport (TensorFlowLibrary)]
+ private static extern unsafe void TFL_DeleteInterpreter(TFL_Interpreter interpreter);
+
+ [DllImport (TensorFlowLibrary)]
+ private static extern unsafe int TFL_InterpreterGetInputTensorCount(
+ TFL_Interpreter interpreter);
+
+ [DllImport (TensorFlowLibrary)]
+ private static extern unsafe TFL_Tensor TFL_InterpreterGetInputTensor(
+ TFL_Interpreter interpreter,
+ int input_index);
+
+ [DllImport (TensorFlowLibrary)]
+ private static extern unsafe int TFL_InterpreterResizeInputTensor(
+ TFL_Interpreter interpreter,
+ int input_index,
+ int[] input_dims,
+ int input_dims_size);
+
+ [DllImport (TensorFlowLibrary)]
+ private static extern unsafe int TFL_InterpreterAllocateTensors(
+ TFL_Interpreter interpreter);
+
+ [DllImport (TensorFlowLibrary)]
+ private static extern unsafe int TFL_InterpreterInvoke(TFL_Interpreter interpreter);
+
+ [DllImport (TensorFlowLibrary)]
+ private static extern unsafe int TFL_InterpreterGetOutputTensorCount(
+ TFL_Interpreter interpreter);
+
+ [DllImport (TensorFlowLibrary)]
+ private static extern unsafe TFL_Tensor TFL_InterpreterGetOutputTensor(
+ TFL_Interpreter interpreter,
+ int output_index);
+
+ [DllImport (TensorFlowLibrary)]
+ private static extern unsafe int TFL_TensorCopyFromBuffer(
+ TFL_Tensor tensor,
+ IntPtr input_data,
+ int input_data_size);
+
+ [DllImport (TensorFlowLibrary)]
+ private static extern unsafe int TFL_TensorCopyToBuffer(
+ TFL_Tensor tensor,
+ IntPtr output_data,
+ int output_data_size);
+
+ #endregion
+ }
+}
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/SDK/Scripts/Interpreter.cs.meta b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/SDK/Scripts/Interpreter.cs.meta
new file mode 100644
index 0000000000..5ec84ef7f7
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/Assets/TensorFlowLite/SDK/Scripts/Interpreter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0bbaf59e6ac914ed1b28174fb9008a09
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/AudioManager.asset b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/AudioManager.asset
new file mode 100644
index 0000000000..da6112576a
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/AudioManager.asset
@@ -0,0 +1,17 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!11 &1
+AudioManager:
+ m_ObjectHideFlags: 0
+ m_Volume: 1
+ Rolloff Scale: 1
+ Doppler Factor: 1
+ Default Speaker Mode: 2
+ m_SampleRate: 0
+ m_DSPBufferSize: 0
+ m_VirtualVoiceCount: 512
+ m_RealVoiceCount: 32
+ m_SpatializerPlugin:
+ m_AmbisonicDecoderPlugin:
+ m_DisableAudio: 0
+ m_VirtualizeEffects: 1
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/ClusterInputManager.asset b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/ClusterInputManager.asset
new file mode 100644
index 0000000000..e7886b266a
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/ClusterInputManager.asset
@@ -0,0 +1,6 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!236 &1
+ClusterInputManager:
+ m_ObjectHideFlags: 0
+ m_Inputs: []
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/DynamicsManager.asset b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/DynamicsManager.asset
new file mode 100644
index 0000000000..78992f08c7
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/DynamicsManager.asset
@@ -0,0 +1,29 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!55 &1
+PhysicsManager:
+ m_ObjectHideFlags: 0
+ serializedVersion: 7
+ m_Gravity: {x: 0, y: -9.81, z: 0}
+ m_DefaultMaterial: {fileID: 0}
+ m_BounceThreshold: 2
+ m_SleepThreshold: 0.005
+ m_DefaultContactOffset: 0.01
+ m_DefaultSolverIterations: 6
+ m_DefaultSolverVelocityIterations: 1
+ m_QueriesHitBackfaces: 0
+ m_QueriesHitTriggers: 1
+ m_EnableAdaptiveForce: 0
+ m_ClothInterCollisionDistance: 0
+ m_ClothInterCollisionStiffness: 0
+ m_ContactsGeneration: 1
+ m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ m_AutoSimulation: 1
+ m_AutoSyncTransforms: 1
+ m_ClothInterCollisionSettingsToggle: 0
+ m_ContactPairsMode: 0
+ m_BroadphaseType: 0
+ m_WorldBounds:
+ m_Center: {x: 0, y: 0, z: 0}
+ m_Extent: {x: 250, y: 250, z: 250}
+ m_WorldSubdivisions: 8
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/EditorBuildSettings.asset b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/EditorBuildSettings.asset
new file mode 100644
index 0000000000..6dc24f7dfd
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/EditorBuildSettings.asset
@@ -0,0 +1,7 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1045 &1
+EditorBuildSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_Scenes: []
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/EditorSettings.asset b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/EditorSettings.asset
new file mode 100644
index 0000000000..fcd016402f
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/EditorSettings.asset
@@ -0,0 +1,21 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!159 &1
+EditorSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 7
+ m_ExternalVersionControlSupport: Visible Meta Files
+ m_SerializationMode: 2
+ m_LineEndingsForNewScripts: 1
+ m_DefaultBehaviorMode: 1
+ m_SpritePackerMode: 4
+ m_SpritePackerPaddingPower: 1
+ m_EtcTextureCompressorBehavior: 1
+ m_EtcTextureFastCompressor: 1
+ m_EtcTextureNormalCompressor: 2
+ m_EtcTextureBestCompressor: 4
+ m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;rsp
+ m_ProjectGenerationRootNamespace:
+ m_UserGeneratedProjectSuffix:
+ m_CollabEditorSettings:
+ inProgressEnabled: 1
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/GraphicsSettings.asset b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/GraphicsSettings.asset
new file mode 100644
index 0000000000..74d7b532b0
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/GraphicsSettings.asset
@@ -0,0 +1,61 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!30 &1
+GraphicsSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 12
+ m_Deferred:
+ m_Mode: 1
+ m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0}
+ m_DeferredReflections:
+ m_Mode: 1
+ m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0}
+ m_ScreenSpaceShadows:
+ m_Mode: 1
+ m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0}
+ m_LegacyDeferred:
+ m_Mode: 1
+ m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0}
+ m_DepthNormals:
+ m_Mode: 1
+ m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0}
+ m_MotionVectors:
+ m_Mode: 1
+ m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0}
+ m_LightHalo:
+ m_Mode: 1
+ m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0}
+ m_LensFlare:
+ m_Mode: 1
+ m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0}
+ m_AlwaysIncludedShaders:
+ - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0}
+ - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0}
+ - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0}
+ - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0}
+ - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
+ - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0}
+ m_PreloadedShaders: []
+ m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000,
+ type: 0}
+ m_CustomRenderPipeline: {fileID: 0}
+ m_TransparencySortMode: 0
+ m_TransparencySortAxis: {x: 0, y: 0, z: 1}
+ m_DefaultRenderingPath: 1
+ m_DefaultMobileRenderingPath: 1
+ m_TierSettings: []
+ m_LightmapStripping: 0
+ m_FogStripping: 0
+ m_InstancingStripping: 0
+ m_LightmapKeepPlain: 1
+ m_LightmapKeepDirCombined: 1
+ m_LightmapKeepDynamicPlain: 1
+ m_LightmapKeepDynamicDirCombined: 1
+ m_LightmapKeepShadowMask: 1
+ m_LightmapKeepSubtractive: 1
+ m_FogKeepLinear: 1
+ m_FogKeepExp: 1
+ m_FogKeepExp2: 1
+ m_AlbedoSwatchInfos: []
+ m_LightsUseLinearIntensity: 0
+ m_LightsUseColorTemperature: 0
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/InputManager.asset b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/InputManager.asset
new file mode 100644
index 0000000000..17c8f538e2
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/InputManager.asset
@@ -0,0 +1,295 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!13 &1
+InputManager:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_Axes:
+ - serializedVersion: 3
+ m_Name: Horizontal
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton: left
+ positiveButton: right
+ altNegativeButton: a
+ altPositiveButton: d
+ gravity: 3
+ dead: 0.001
+ sensitivity: 3
+ snap: 1
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Vertical
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton: down
+ positiveButton: up
+ altNegativeButton: s
+ altPositiveButton: w
+ gravity: 3
+ dead: 0.001
+ sensitivity: 3
+ snap: 1
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Fire1
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: left ctrl
+ altNegativeButton:
+ altPositiveButton: mouse 0
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Fire2
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: left alt
+ altNegativeButton:
+ altPositiveButton: mouse 1
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Fire3
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: left shift
+ altNegativeButton:
+ altPositiveButton: mouse 2
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Jump
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: space
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Mouse X
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton:
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 0
+ dead: 0
+ sensitivity: 0.1
+ snap: 0
+ invert: 0
+ type: 1
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Mouse Y
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton:
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 0
+ dead: 0
+ sensitivity: 0.1
+ snap: 0
+ invert: 0
+ type: 1
+ axis: 1
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Mouse ScrollWheel
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton:
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 0
+ dead: 0
+ sensitivity: 0.1
+ snap: 0
+ invert: 0
+ type: 1
+ axis: 2
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Horizontal
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton:
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 0
+ dead: 0.19
+ sensitivity: 1
+ snap: 0
+ invert: 0
+ type: 2
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Vertical
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton:
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 0
+ dead: 0.19
+ sensitivity: 1
+ snap: 0
+ invert: 1
+ type: 2
+ axis: 1
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Fire1
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: joystick button 0
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Fire2
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: joystick button 1
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Fire3
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: joystick button 2
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Jump
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: joystick button 3
+ altNegativeButton:
+ altPositiveButton:
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Submit
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: return
+ altNegativeButton:
+ altPositiveButton: joystick button 0
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Submit
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: enter
+ altNegativeButton:
+ altPositiveButton: space
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
+ - serializedVersion: 3
+ m_Name: Cancel
+ descriptiveName:
+ descriptiveNegativeName:
+ negativeButton:
+ positiveButton: escape
+ altNegativeButton:
+ altPositiveButton: joystick button 1
+ gravity: 1000
+ dead: 0.001
+ sensitivity: 1000
+ snap: 0
+ invert: 0
+ type: 0
+ axis: 0
+ joyNum: 0
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/NavMeshAreas.asset b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/NavMeshAreas.asset
new file mode 100644
index 0000000000..3b0b7c3d18
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/NavMeshAreas.asset
@@ -0,0 +1,91 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!126 &1
+NavMeshProjectSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ areas:
+ - name: Walkable
+ cost: 1
+ - name: Not Walkable
+ cost: 1
+ - name: Jump
+ cost: 2
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ - name:
+ cost: 1
+ m_LastAgentTypeID: -887442657
+ m_Settings:
+ - serializedVersion: 2
+ agentTypeID: 0
+ agentRadius: 0.5
+ agentHeight: 2
+ agentSlope: 45
+ agentClimb: 0.75
+ ledgeDropHeight: 0
+ maxJumpAcrossDistance: 0
+ minRegionArea: 2
+ manualCellSize: 0
+ cellSize: 0.16666667
+ manualTileSize: 0
+ tileSize: 256
+ accuratePlacement: 0
+ debug:
+ m_Flags: 0
+ m_SettingNames:
+ - Humanoid
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/NetworkManager.asset b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/NetworkManager.asset
new file mode 100644
index 0000000000..5dc6a831d9
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/NetworkManager.asset
@@ -0,0 +1,8 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!149 &1
+NetworkManager:
+ m_ObjectHideFlags: 0
+ m_DebugLevel: 0
+ m_Sendrate: 15
+ m_AssetToPrefab: {}
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/Physics2DSettings.asset b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/Physics2DSettings.asset
new file mode 100644
index 0000000000..132ee6bc86
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/Physics2DSettings.asset
@@ -0,0 +1,37 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!19 &1
+Physics2DSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 3
+ m_Gravity: {x: 0, y: -9.81}
+ m_DefaultMaterial: {fileID: 0}
+ m_VelocityIterations: 8
+ m_PositionIterations: 3
+ m_VelocityThreshold: 1
+ m_MaxLinearCorrection: 0.2
+ m_MaxAngularCorrection: 8
+ m_MaxTranslationSpeed: 100
+ m_MaxRotationSpeed: 360
+ m_BaumgarteScale: 0.2
+ m_BaumgarteTimeOfImpactScale: 0.75
+ m_TimeToSleep: 0.5
+ m_LinearSleepTolerance: 0.01
+ m_AngularSleepTolerance: 2
+ m_DefaultContactOffset: 0.01
+ m_AutoSimulation: 1
+ m_QueriesHitTriggers: 1
+ m_QueriesStartInColliders: 1
+ m_ChangeStopsCallbacks: 0
+ m_CallbacksOnDisable: 1
+ m_AutoSyncTransforms: 1
+ m_AlwaysShowColliders: 0
+ m_ShowColliderSleep: 1
+ m_ShowColliderContacts: 0
+ m_ShowColliderAABB: 0
+ m_ContactArrowScale: 0.2
+ m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412}
+ m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432}
+ m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745}
+ m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804}
+ m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/ProjectSettings.asset b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/ProjectSettings.asset
new file mode 100644
index 0000000000..3fbfab76c1
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/ProjectSettings.asset
@@ -0,0 +1,641 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!129 &1
+PlayerSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 14
+ productGUID: a084943b991dd4597b140f4ce2b41c65
+ AndroidProfiler: 0
+ AndroidFilterTouchesWhenObscured: 0
+ defaultScreenOrientation: 4
+ targetDevice: 2
+ useOnDemandResources: 0
+ accelerometerFrequency: 60
+ companyName: DefaultCompany
+ productName: TensorFlowLitePlugin
+ defaultCursor: {fileID: 0}
+ cursorHotspot: {x: 0, y: 0}
+ m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1}
+ m_ShowUnitySplashScreen: 1
+ m_ShowUnitySplashLogo: 1
+ m_SplashScreenOverlayOpacity: 1
+ m_SplashScreenAnimation: 1
+ m_SplashScreenLogoStyle: 1
+ m_SplashScreenDrawMode: 0
+ m_SplashScreenBackgroundAnimationZoom: 1
+ m_SplashScreenLogoAnimationZoom: 1
+ m_SplashScreenBackgroundLandscapeAspect: 1
+ m_SplashScreenBackgroundPortraitAspect: 1
+ m_SplashScreenBackgroundLandscapeUvs:
+ serializedVersion: 2
+ x: 0
+ y: 0
+ width: 1
+ height: 1
+ m_SplashScreenBackgroundPortraitUvs:
+ serializedVersion: 2
+ x: 0
+ y: 0
+ width: 1
+ height: 1
+ m_SplashScreenLogos: []
+ m_VirtualRealitySplashScreen: {fileID: 0}
+ m_HolographicTrackingLossScreen: {fileID: 0}
+ defaultScreenWidth: 1024
+ defaultScreenHeight: 768
+ defaultScreenWidthWeb: 960
+ defaultScreenHeightWeb: 600
+ m_StereoRenderingPath: 0
+ m_ActiveColorSpace: 0
+ m_MTRendering: 1
+ m_StackTraceTypes: 010000000100000001000000010000000100000001000000
+ iosShowActivityIndicatorOnLoading: -1
+ androidShowActivityIndicatorOnLoading: -1
+ tizenShowActivityIndicatorOnLoading: -1
+ iosAppInBackgroundBehavior: 0
+ displayResolutionDialog: 1
+ iosAllowHTTPDownload: 1
+ allowedAutorotateToPortrait: 1
+ allowedAutorotateToPortraitUpsideDown: 1
+ allowedAutorotateToLandscapeRight: 1
+ allowedAutorotateToLandscapeLeft: 1
+ useOSAutorotation: 1
+ use32BitDisplayBuffer: 1
+ preserveFramebufferAlpha: 0
+ disableDepthAndStencilBuffers: 0
+ androidBlitType: 0
+ defaultIsFullScreen: 1
+ defaultIsNativeResolution: 1
+ macRetinaSupport: 1
+ runInBackground: 0
+ captureSingleScreen: 0
+ muteOtherAudioSources: 0
+ Prepare IOS For Recording: 0
+ Force IOS Speakers When Recording: 0
+ deferSystemGesturesMode: 0
+ hideHomeButton: 0
+ submitAnalytics: 1
+ usePlayerLog: 1
+ bakeCollisionMeshes: 0
+ forceSingleInstance: 0
+ resizableWindow: 0
+ useMacAppStoreValidation: 0
+ macAppStoreCategory: public.app-category.games
+ gpuSkinning: 0
+ graphicsJobs: 0
+ xboxPIXTextureCapture: 0
+ xboxEnableAvatar: 0
+ xboxEnableKinect: 0
+ xboxEnableKinectAutoTracking: 0
+ xboxEnableFitness: 0
+ visibleInBackground: 1
+ allowFullscreenSwitch: 1
+ graphicsJobMode: 0
+ macFullscreenMode: 2
+ d3d11FullscreenMode: 1
+ xboxSpeechDB: 0
+ xboxEnableHeadOrientation: 0
+ xboxEnableGuest: 0
+ xboxEnablePIXSampling: 0
+ metalFramebufferOnly: 0
+ n3dsDisableStereoscopicView: 0
+ n3dsEnableSharedListOpt: 1
+ n3dsEnableVSync: 0
+ xboxOneResolution: 0
+ xboxOneSResolution: 0
+ xboxOneXResolution: 3
+ xboxOneMonoLoggingLevel: 0
+ xboxOneLoggingLevel: 1
+ xboxOneDisableEsram: 0
+ xboxOnePresentImmediateThreshold: 0
+ videoMemoryForVertexBuffers: 0
+ psp2PowerMode: 0
+ psp2AcquireBGM: 1
+ wiiUTVResolution: 0
+ wiiUGamePadMSAA: 1
+ wiiUSupportsNunchuk: 0
+ wiiUSupportsClassicController: 0
+ wiiUSupportsBalanceBoard: 0
+ wiiUSupportsMotionPlus: 0
+ wiiUSupportsProController: 0
+ wiiUAllowScreenCapture: 1
+ wiiUControllerCount: 0
+ m_SupportedAspectRatios:
+ 4:3: 1
+ 5:4: 1
+ 16:10: 1
+ 16:9: 1
+ Others: 1
+ bundleVersion: 1.0
+ preloadedAssets: []
+ metroInputSource: 0
+ wsaTransparentSwapchain: 0
+ m_HolographicPauseOnTrackingLoss: 1
+ xboxOneDisableKinectGpuReservation: 0
+ xboxOneEnable7thCore: 0
+ vrSettings:
+ cardboard:
+ depthFormat: 0
+ enableTransitionView: 0
+ daydream:
+ depthFormat: 0
+ useSustainedPerformanceMode: 0
+ enableVideoLayer: 0
+ useProtectedVideoMemory: 0
+ minimumSupportedHeadTracking: 0
+ maximumSupportedHeadTracking: 1
+ hololens:
+ depthFormat: 1
+ depthBufferSharingEnabled: 0
+ oculus:
+ sharedDepthBuffer: 0
+ dashSupport: 0
+ protectGraphicsMemory: 0
+ useHDRDisplay: 0
+ m_ColorGamuts: 00000000
+ targetPixelDensity: 30
+ resolutionScalingMode: 0
+ androidSupportedAspectRatio: 1
+ androidMaxAspectRatio: 2.1
+ applicationIdentifier: {}
+ buildNumber: {}
+ AndroidBundleVersionCode: 1
+ AndroidMinSdkVersion: 16
+ AndroidTargetSdkVersion: 0
+ AndroidPreferredInstallLocation: 1
+ aotOptions:
+ stripEngineCode: 1
+ iPhoneStrippingLevel: 0
+ iPhoneScriptCallOptimization: 0
+ ForceInternetPermission: 0
+ ForceSDCardPermission: 0
+ CreateWallpaper: 0
+ APKExpansionFiles: 0
+ keepLoadedShadersAlive: 0
+ StripUnusedMeshComponents: 0
+ VertexChannelCompressionMask:
+ serializedVersion: 2
+ m_Bits: 238
+ iPhoneSdkVersion: 988
+ iOSTargetOSVersionString: 7.0
+ tvOSSdkVersion: 0
+ tvOSRequireExtendedGameController: 0
+ tvOSTargetOSVersionString: 9.0
+ uIPrerenderedIcon: 0
+ uIRequiresPersistentWiFi: 0
+ uIRequiresFullScreen: 1
+ uIStatusBarHidden: 1
+ uIExitOnSuspend: 0
+ uIStatusBarStyle: 0
+ iPhoneSplashScreen: {fileID: 0}
+ iPhoneHighResSplashScreen: {fileID: 0}
+ iPhoneTallHighResSplashScreen: {fileID: 0}
+ iPhone47inSplashScreen: {fileID: 0}
+ iPhone55inPortraitSplashScreen: {fileID: 0}
+ iPhone55inLandscapeSplashScreen: {fileID: 0}
+ iPhone58inPortraitSplashScreen: {fileID: 0}
+ iPhone58inLandscapeSplashScreen: {fileID: 0}
+ iPadPortraitSplashScreen: {fileID: 0}
+ iPadHighResPortraitSplashScreen: {fileID: 0}
+ iPadLandscapeSplashScreen: {fileID: 0}
+ iPadHighResLandscapeSplashScreen: {fileID: 0}
+ appleTVSplashScreen: {fileID: 0}
+ appleTVSplashScreen2x: {fileID: 0}
+ tvOSSmallIconLayers: []
+ tvOSSmallIconLayers2x: []
+ tvOSLargeIconLayers: []
+ tvOSTopShelfImageLayers: []
+ tvOSTopShelfImageLayers2x: []
+ tvOSTopShelfImageWideLayers: []
+ tvOSTopShelfImageWideLayers2x: []
+ iOSLaunchScreenType: 0
+ iOSLaunchScreenPortrait: {fileID: 0}
+ iOSLaunchScreenLandscape: {fileID: 0}
+ iOSLaunchScreenBackgroundColor:
+ serializedVersion: 2
+ rgba: 0
+ iOSLaunchScreenFillPct: 100
+ iOSLaunchScreenSize: 100
+ iOSLaunchScreenCustomXibPath:
+ iOSLaunchScreeniPadType: 0
+ iOSLaunchScreeniPadImage: {fileID: 0}
+ iOSLaunchScreeniPadBackgroundColor:
+ serializedVersion: 2
+ rgba: 0
+ iOSLaunchScreeniPadFillPct: 100
+ iOSLaunchScreeniPadSize: 100
+ iOSLaunchScreeniPadCustomXibPath:
+ iOSUseLaunchScreenStoryboard: 0
+ iOSLaunchScreenCustomStoryboardPath:
+ iOSDeviceRequirements: []
+ iOSURLSchemes: []
+ iOSBackgroundModes: 0
+ iOSMetalForceHardShadows: 0
+ metalEditorSupport: 1
+ metalAPIValidation: 1
+ iOSRenderExtraFrameOnPause: 0
+ appleDeveloperTeamID:
+ iOSManualSigningProvisioningProfileID:
+ tvOSManualSigningProvisioningProfileID:
+ appleEnableAutomaticSigning: 0
+ clonedFromGUID: 00000000000000000000000000000000
+ AndroidTargetDevice: 0
+ AndroidSplashScreenScale: 0
+ androidSplashScreen: {fileID: 0}
+ AndroidKeystoreName:
+ AndroidKeyaliasName:
+ AndroidTVCompatibility: 1
+ AndroidIsGame: 1
+ AndroidEnableTango: 0
+ androidEnableBanner: 1
+ androidUseLowAccuracyLocation: 0
+ m_AndroidBanners:
+ - width: 320
+ height: 180
+ banner: {fileID: 0}
+ androidGamepadSupportLevel: 0
+ resolutionDialogBanner: {fileID: 0}
+ m_BuildTargetIcons: []
+ m_BuildTargetBatching: []
+ m_BuildTargetGraphicsAPIs: []
+ m_BuildTargetVRSettings: []
+ m_BuildTargetEnableVuforiaSettings: []
+ openGLRequireES31: 0
+ openGLRequireES31AEP: 0
+ m_TemplateCustomTags: {}
+ mobileMTRendering:
+ Android: 1
+ iPhone: 1
+ tvOS: 1
+ m_BuildTargetGroupLightmapEncodingQuality: []
+ wiiUTitleID: 0005000011000000
+ wiiUGroupID: 00010000
+ wiiUCommonSaveSize: 4096
+ wiiUAccountSaveSize: 2048
+ wiiUOlvAccessKey: 0
+ wiiUTinCode: 0
+ wiiUJoinGameId: 0
+ wiiUJoinGameModeMask: 0000000000000000
+ wiiUCommonBossSize: 0
+ wiiUAccountBossSize: 0
+ wiiUAddOnUniqueIDs: []
+ wiiUMainThreadStackSize: 3072
+ wiiULoaderThreadStackSize: 1024
+ wiiUSystemHeapSize: 128
+ wiiUTVStartupScreen: {fileID: 0}
+ wiiUGamePadStartupScreen: {fileID: 0}
+ wiiUDrcBufferDisabled: 0
+ wiiUProfilerLibPath:
+ playModeTestRunnerEnabled: 0
+ actionOnDotNetUnhandledException: 1
+ enableInternalProfiler: 0
+ logObjCUncaughtExceptions: 1
+ enableCrashReportAPI: 0
+ cameraUsageDescription:
+ locationUsageDescription:
+ microphoneUsageDescription:
+ switchNetLibKey:
+ switchSocketMemoryPoolSize: 6144
+ switchSocketAllocatorPoolSize: 128
+ switchSocketConcurrencyLimit: 14
+ switchScreenResolutionBehavior: 2
+ switchUseCPUProfiler: 0
+ switchApplicationID: 0x01004b9000490000
+ switchNSODependencies:
+ switchTitleNames_0:
+ switchTitleNames_1:
+ switchTitleNames_2:
+ switchTitleNames_3:
+ switchTitleNames_4:
+ switchTitleNames_5:
+ switchTitleNames_6:
+ switchTitleNames_7:
+ switchTitleNames_8:
+ switchTitleNames_9:
+ switchTitleNames_10:
+ switchTitleNames_11:
+ switchTitleNames_12:
+ switchTitleNames_13:
+ switchTitleNames_14:
+ switchPublisherNames_0:
+ switchPublisherNames_1:
+ switchPublisherNames_2:
+ switchPublisherNames_3:
+ switchPublisherNames_4:
+ switchPublisherNames_5:
+ switchPublisherNames_6:
+ switchPublisherNames_7:
+ switchPublisherNames_8:
+ switchPublisherNames_9:
+ switchPublisherNames_10:
+ switchPublisherNames_11:
+ switchPublisherNames_12:
+ switchPublisherNames_13:
+ switchPublisherNames_14:
+ switchIcons_0: {fileID: 0}
+ switchIcons_1: {fileID: 0}
+ switchIcons_2: {fileID: 0}
+ switchIcons_3: {fileID: 0}
+ switchIcons_4: {fileID: 0}
+ switchIcons_5: {fileID: 0}
+ switchIcons_6: {fileID: 0}
+ switchIcons_7: {fileID: 0}
+ switchIcons_8: {fileID: 0}
+ switchIcons_9: {fileID: 0}
+ switchIcons_10: {fileID: 0}
+ switchIcons_11: {fileID: 0}
+ switchIcons_12: {fileID: 0}
+ switchIcons_13: {fileID: 0}
+ switchIcons_14: {fileID: 0}
+ switchSmallIcons_0: {fileID: 0}
+ switchSmallIcons_1: {fileID: 0}
+ switchSmallIcons_2: {fileID: 0}
+ switchSmallIcons_3: {fileID: 0}
+ switchSmallIcons_4: {fileID: 0}
+ switchSmallIcons_5: {fileID: 0}
+ switchSmallIcons_6: {fileID: 0}
+ switchSmallIcons_7: {fileID: 0}
+ switchSmallIcons_8: {fileID: 0}
+ switchSmallIcons_9: {fileID: 0}
+ switchSmallIcons_10: {fileID: 0}
+ switchSmallIcons_11: {fileID: 0}
+ switchSmallIcons_12: {fileID: 0}
+ switchSmallIcons_13: {fileID: 0}
+ switchSmallIcons_14: {fileID: 0}
+ switchManualHTML:
+ switchAccessibleURLs:
+ switchLegalInformation:
+ switchMainThreadStackSize: 1048576
+ switchPresenceGroupId:
+ switchLogoHandling: 0
+ switchReleaseVersion: 0
+ switchDisplayVersion: 1.0.0
+ switchStartupUserAccount: 0
+ switchTouchScreenUsage: 0
+ switchSupportedLanguagesMask: 0
+ switchLogoType: 0
+ switchApplicationErrorCodeCategory:
+ switchUserAccountSaveDataSize: 0
+ switchUserAccountSaveDataJournalSize: 0
+ switchApplicationAttribute: 0
+ switchCardSpecSize: -1
+ switchCardSpecClock: -1
+ switchRatingsMask: 0
+ switchRatingsInt_0: 0
+ switchRatingsInt_1: 0
+ switchRatingsInt_2: 0
+ switchRatingsInt_3: 0
+ switchRatingsInt_4: 0
+ switchRatingsInt_5: 0
+ switchRatingsInt_6: 0
+ switchRatingsInt_7: 0
+ switchRatingsInt_8: 0
+ switchRatingsInt_9: 0
+ switchRatingsInt_10: 0
+ switchRatingsInt_11: 0
+ switchLocalCommunicationIds_0:
+ switchLocalCommunicationIds_1:
+ switchLocalCommunicationIds_2:
+ switchLocalCommunicationIds_3:
+ switchLocalCommunicationIds_4:
+ switchLocalCommunicationIds_5:
+ switchLocalCommunicationIds_6:
+ switchLocalCommunicationIds_7:
+ switchParentalControl: 0
+ switchAllowsScreenshot: 1
+ switchAllowsVideoCapturing: 1
+ switchAllowsRuntimeAddOnContentInstall: 0
+ switchDataLossConfirmation: 0
+ switchSupportedNpadStyles: 3
+ switchSocketConfigEnabled: 0
+ switchTcpInitialSendBufferSize: 32
+ switchTcpInitialReceiveBufferSize: 64
+ switchTcpAutoSendBufferSizeMax: 256
+ switchTcpAutoReceiveBufferSizeMax: 256
+ switchUdpSendBufferSize: 9
+ switchUdpReceiveBufferSize: 42
+ switchSocketBufferEfficiency: 4
+ switchSocketInitializeEnabled: 1
+ switchNetworkInterfaceManagerInitializeEnabled: 1
+ switchPlayerConnectionEnabled: 1
+ ps4NPAgeRating: 12
+ ps4NPTitleSecret:
+ ps4NPTrophyPackPath:
+ ps4ParentalLevel: 11
+ ps4ContentID: ED1633-NPXX51362_00-0000000000000000
+ ps4Category: 0
+ ps4MasterVersion: 01.00
+ ps4AppVersion: 01.00
+ ps4AppType: 0
+ ps4ParamSfxPath:
+ ps4VideoOutPixelFormat: 0
+ ps4VideoOutInitialWidth: 1920
+ ps4VideoOutBaseModeInitialWidth: 1920
+ ps4VideoOutReprojectionRate: 60
+ ps4PronunciationXMLPath:
+ ps4PronunciationSIGPath:
+ ps4BackgroundImagePath:
+ ps4StartupImagePath:
+ ps4StartupImagesFolder:
+ ps4IconImagesFolder:
+ ps4SaveDataImagePath:
+ ps4SdkOverride:
+ ps4BGMPath:
+ ps4ShareFilePath:
+ ps4ShareOverlayImagePath:
+ ps4PrivacyGuardImagePath:
+ ps4NPtitleDatPath:
+ ps4RemotePlayKeyAssignment: -1
+ ps4RemotePlayKeyMappingDir:
+ ps4PlayTogetherPlayerCount: 0
+ ps4EnterButtonAssignment: 1
+ ps4ApplicationParam1: 0
+ ps4ApplicationParam2: 0
+ ps4ApplicationParam3: 0
+ ps4ApplicationParam4: 0
+ ps4DownloadDataSize: 0
+ ps4GarlicHeapSize: 2048
+ ps4ProGarlicHeapSize: 2560
+ ps4Passcode: d3hjjul8UhK6ZnQCEBYYQPozR9sQV066
+ ps4pnSessions: 1
+ ps4pnPresence: 1
+ ps4pnFriends: 1
+ ps4pnGameCustomData: 1
+ playerPrefsSupport: 0
+ restrictedAudioUsageRights: 0
+ ps4UseResolutionFallback: 0
+ ps4ReprojectionSupport: 0
+ ps4UseAudio3dBackend: 0
+ ps4SocialScreenEnabled: 0
+ ps4ScriptOptimizationLevel: 0
+ ps4Audio3dVirtualSpeakerCount: 14
+ ps4attribCpuUsage: 0
+ ps4PatchPkgPath:
+ ps4PatchLatestPkgPath:
+ ps4PatchChangeinfoPath:
+ ps4PatchDayOne: 0
+ ps4attribUserManagement: 0
+ ps4attribMoveSupport: 0
+ ps4attrib3DSupport: 0
+ ps4attribShareSupport: 0
+ ps4attribExclusiveVR: 0
+ ps4disableAutoHideSplash: 0
+ ps4videoRecordingFeaturesUsed: 0
+ ps4contentSearchFeaturesUsed: 0
+ ps4attribEyeToEyeDistanceSettingVR: 0
+ ps4IncludedModules: []
+ monoEnv:
+ psp2Splashimage: {fileID: 0}
+ psp2NPTrophyPackPath:
+ psp2NPSupportGBMorGJP: 0
+ psp2NPAgeRating: 12
+ psp2NPTitleDatPath:
+ psp2NPCommsID:
+ psp2NPCommunicationsID:
+ psp2NPCommsPassphrase:
+ psp2NPCommsSig:
+ psp2ParamSfxPath:
+ psp2ManualPath:
+ psp2LiveAreaGatePath:
+ psp2LiveAreaBackroundPath:
+ psp2LiveAreaPath:
+ psp2LiveAreaTrialPath:
+ psp2PatchChangeInfoPath:
+ psp2PatchOriginalPackage:
+ psp2PackagePassword: 3onkgZsAECEn0fzCoWiCtWCKe4l74pE5
+ psp2KeystoneFile:
+ psp2MemoryExpansionMode: 0
+ psp2DRMType: 0
+ psp2StorageType: 0
+ psp2MediaCapacity: 0
+ psp2DLCConfigPath:
+ psp2ThumbnailPath:
+ psp2BackgroundPath:
+ psp2SoundPath:
+ psp2TrophyCommId:
+ psp2TrophyPackagePath:
+ psp2PackagedResourcesPath:
+ psp2SaveDataQuota: 10240
+ psp2ParentalLevel: 1
+ psp2ShortTitle: Not Set
+ psp2ContentID: IV0000-ABCD12345_00-0123456789ABCDEF
+ psp2Category: 0
+ psp2MasterVersion: 01.00
+ psp2AppVersion: 01.00
+ psp2TVBootMode: 0
+ psp2EnterButtonAssignment: 2
+ psp2TVDisableEmu: 0
+ psp2AllowTwitterDialog: 1
+ psp2Upgradable: 0
+ psp2HealthWarning: 0
+ psp2UseLibLocation: 0
+ psp2InfoBarOnStartup: 0
+ psp2InfoBarColor: 0
+ psp2ScriptOptimizationLevel: 0
+ psmSplashimage: {fileID: 0}
+ splashScreenBackgroundSourceLandscape: {fileID: 0}
+ splashScreenBackgroundSourcePortrait: {fileID: 0}
+ spritePackerPolicy:
+ webGLMemorySize: 256
+ webGLExceptionSupport: 1
+ webGLNameFilesAsHashes: 0
+ webGLDataCaching: 0
+ webGLDebugSymbols: 0
+ webGLEmscriptenArgs:
+ webGLModulesDirectory:
+ webGLTemplate: APPLICATION:Default
+ webGLAnalyzeBuildSize: 0
+ webGLUseEmbeddedResources: 0
+ webGLUseWasm: 0
+ webGLCompressionFormat: 1
+ scriptingDefineSymbols: {}
+ platformArchitecture: {}
+ scriptingBackend: {}
+ incrementalIl2cppBuild: {}
+ additionalIl2CppArgs:
+ scriptingRuntimeVersion: 0
+ apiCompatibilityLevelPerPlatform: {}
+ m_RenderingPath: 1
+ m_MobileRenderingPath: 1
+ metroPackageName: TensorFlowLitePlugin
+ metroPackageVersion:
+ metroCertificatePath:
+ metroCertificatePassword:
+ metroCertificateSubject:
+ metroCertificateIssuer:
+ metroCertificateNotAfter: 0000000000000000
+ metroApplicationDescription: TensorFlowLitePlugin
+ wsaImages: {}
+ metroTileShortName:
+ metroCommandLineArgsFile:
+ metroTileShowName: 0
+ metroMediumTileShowName: 0
+ metroLargeTileShowName: 0
+ metroWideTileShowName: 0
+ metroDefaultTileSize: 1
+ metroTileForegroundText: 2
+ metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0}
+ metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628,
+ a: 1}
+ metroSplashScreenUseBackgroundColor: 0
+ platformCapabilities: {}
+ metroFTAName:
+ metroFTAFileTypes: []
+ metroProtocolName:
+ metroCompilationOverrides: 1
+ tizenProductDescription:
+ tizenProductURL:
+ tizenSigningProfileName:
+ tizenGPSPermissions: 0
+ tizenMicrophonePermissions: 0
+ tizenDeploymentTarget:
+ tizenDeploymentTargetType: -1
+ tizenMinOSVersion: 1
+ n3dsUseExtSaveData: 0
+ n3dsCompressStaticMem: 1
+ n3dsExtSaveDataNumber: 0x12345
+ n3dsStackSize: 131072
+ n3dsTargetPlatform: 2
+ n3dsRegion: 7
+ n3dsMediaSize: 0
+ n3dsLogoStyle: 3
+ n3dsTitle: GameName
+ n3dsProductCode:
+ n3dsApplicationId: 0xFF3FF
+ XboxOneProductId:
+ XboxOneUpdateKey:
+ XboxOneSandboxId:
+ XboxOneContentId:
+ XboxOneTitleId:
+ XboxOneSCId:
+ XboxOneGameOsOverridePath:
+ XboxOnePackagingOverridePath:
+ XboxOneAppManifestOverridePath:
+ XboxOnePackageEncryption: 0
+ XboxOnePackageUpdateGranularity: 2
+ XboxOneDescription:
+ XboxOneLanguage:
+ - enus
+ XboxOneCapability: []
+ XboxOneGameRating: {}
+ XboxOneIsContentPackage: 0
+ XboxOneEnableGPUVariability: 0
+ XboxOneSockets: {}
+ XboxOneSplashScreen: {fileID: 0}
+ XboxOneAllowedProductIds: []
+ XboxOnePersistentLocalStorageSize: 0
+ XboxOneXTitleMemory: 8
+ xboxOneScriptCompiler: 0
+ vrEditorSettings:
+ daydream:
+ daydreamIconForeground: {fileID: 0}
+ daydreamIconBackground: {fileID: 0}
+ cloudServicesEnabled: {}
+ facebookSdkVersion: 7.9.4
+ apiCompatibilityLevel: 2
+ cloudProjectId:
+ projectName:
+ organizationId:
+ cloudEnabled: 0
+ enableNativePlatformBackendsForNewInputSystem: 0
+ disableOldInputManagerSupport: 0
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/ProjectVersion.txt b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/ProjectVersion.txt
new file mode 100644
index 0000000000..4a9cfb61ab
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/ProjectVersion.txt
@@ -0,0 +1 @@
+m_EditorVersion: 2017.4.6f1
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/QualitySettings.asset b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/QualitySettings.asset
new file mode 100644
index 0000000000..05daac3c49
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/QualitySettings.asset
@@ -0,0 +1,191 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!47 &1
+QualitySettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 5
+ m_CurrentQuality: 5
+ m_QualitySettings:
+ - serializedVersion: 2
+ name: Very Low
+ pixelLightCount: 0
+ shadows: 0
+ shadowResolution: 0
+ shadowProjection: 1
+ shadowCascades: 1
+ shadowDistance: 15
+ shadowNearPlaneOffset: 3
+ shadowCascade2Split: 0.33333334
+ shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
+ shadowmaskMode: 0
+ blendWeights: 1
+ textureQuality: 1
+ anisotropicTextures: 0
+ antiAliasing: 0
+ softParticles: 0
+ softVegetation: 0
+ realtimeReflectionProbes: 0
+ billboardsFaceCameraPosition: 0
+ vSyncCount: 0
+ lodBias: 0.3
+ maximumLODLevel: 0
+ particleRaycastBudget: 4
+ asyncUploadTimeSlice: 2
+ asyncUploadBufferSize: 4
+ resolutionScalingFixedDPIFactor: 1
+ excludedTargetPlatforms: []
+ - serializedVersion: 2
+ name: Low
+ pixelLightCount: 0
+ shadows: 0
+ shadowResolution: 0
+ shadowProjection: 1
+ shadowCascades: 1
+ shadowDistance: 20
+ shadowNearPlaneOffset: 3
+ shadowCascade2Split: 0.33333334
+ shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
+ shadowmaskMode: 0
+ blendWeights: 2
+ textureQuality: 0
+ anisotropicTextures: 0
+ antiAliasing: 0
+ softParticles: 0
+ softVegetation: 0
+ realtimeReflectionProbes: 0
+ billboardsFaceCameraPosition: 0
+ vSyncCount: 0
+ lodBias: 0.4
+ maximumLODLevel: 0
+ particleRaycastBudget: 16
+ asyncUploadTimeSlice: 2
+ asyncUploadBufferSize: 4
+ resolutionScalingFixedDPIFactor: 1
+ excludedTargetPlatforms: []
+ - serializedVersion: 2
+ name: Medium
+ pixelLightCount: 1
+ shadows: 1
+ shadowResolution: 0
+ shadowProjection: 1
+ shadowCascades: 1
+ shadowDistance: 20
+ shadowNearPlaneOffset: 3
+ shadowCascade2Split: 0.33333334
+ shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
+ shadowmaskMode: 0
+ blendWeights: 2
+ textureQuality: 0
+ anisotropicTextures: 1
+ antiAliasing: 0
+ softParticles: 0
+ softVegetation: 0
+ realtimeReflectionProbes: 0
+ billboardsFaceCameraPosition: 0
+ vSyncCount: 1
+ lodBias: 0.7
+ maximumLODLevel: 0
+ particleRaycastBudget: 64
+ asyncUploadTimeSlice: 2
+ asyncUploadBufferSize: 4
+ resolutionScalingFixedDPIFactor: 1
+ excludedTargetPlatforms: []
+ - serializedVersion: 2
+ name: High
+ pixelLightCount: 2
+ shadows: 2
+ shadowResolution: 1
+ shadowProjection: 1
+ shadowCascades: 2
+ shadowDistance: 40
+ shadowNearPlaneOffset: 3
+ shadowCascade2Split: 0.33333334
+ shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
+ shadowmaskMode: 1
+ blendWeights: 2
+ textureQuality: 0
+ anisotropicTextures: 1
+ antiAliasing: 0
+ softParticles: 0
+ softVegetation: 1
+ realtimeReflectionProbes: 1
+ billboardsFaceCameraPosition: 1
+ vSyncCount: 1
+ lodBias: 1
+ maximumLODLevel: 0
+ particleRaycastBudget: 256
+ asyncUploadTimeSlice: 2
+ asyncUploadBufferSize: 4
+ resolutionScalingFixedDPIFactor: 1
+ excludedTargetPlatforms: []
+ - serializedVersion: 2
+ name: Very High
+ pixelLightCount: 3
+ shadows: 2
+ shadowResolution: 2
+ shadowProjection: 1
+ shadowCascades: 2
+ shadowDistance: 70
+ shadowNearPlaneOffset: 3
+ shadowCascade2Split: 0.33333334
+ shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
+ shadowmaskMode: 1
+ blendWeights: 4
+ textureQuality: 0
+ anisotropicTextures: 2
+ antiAliasing: 2
+ softParticles: 1
+ softVegetation: 1
+ realtimeReflectionProbes: 1
+ billboardsFaceCameraPosition: 1
+ vSyncCount: 1
+ lodBias: 1.5
+ maximumLODLevel: 0
+ particleRaycastBudget: 1024
+ asyncUploadTimeSlice: 2
+ asyncUploadBufferSize: 4
+ resolutionScalingFixedDPIFactor: 1
+ excludedTargetPlatforms: []
+ - serializedVersion: 2
+ name: Ultra
+ pixelLightCount: 4
+ shadows: 2
+ shadowResolution: 2
+ shadowProjection: 1
+ shadowCascades: 4
+ shadowDistance: 150
+ shadowNearPlaneOffset: 3
+ shadowCascade2Split: 0.33333334
+ shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
+ shadowmaskMode: 1
+ blendWeights: 4
+ textureQuality: 0
+ anisotropicTextures: 2
+ antiAliasing: 2
+ softParticles: 1
+ softVegetation: 1
+ realtimeReflectionProbes: 1
+ billboardsFaceCameraPosition: 1
+ vSyncCount: 1
+ lodBias: 2
+ maximumLODLevel: 0
+ particleRaycastBudget: 4096
+ asyncUploadTimeSlice: 2
+ asyncUploadBufferSize: 4
+ resolutionScalingFixedDPIFactor: 1
+ excludedTargetPlatforms: []
+ m_PerPlatformDefaultQuality:
+ Android: 2
+ Nintendo 3DS: 5
+ Nintendo Switch: 5
+ PS4: 5
+ PSM: 5
+ PSP2: 2
+ Standalone: 5
+ Tizen: 2
+ WebGL: 3
+ WiiU: 5
+ Windows Store Apps: 5
+ XboxOne: 5
+ iPhone: 2
+ tvOS: 2
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/TagManager.asset b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/TagManager.asset
new file mode 100644
index 0000000000..1c92a7840e
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/TagManager.asset
@@ -0,0 +1,43 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!78 &1
+TagManager:
+ serializedVersion: 2
+ tags: []
+ layers:
+ - Default
+ - TransparentFX
+ - Ignore Raycast
+ -
+ - Water
+ - UI
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ -
+ m_SortingLayers:
+ - name: Default
+ uniqueID: 0
+ locked: 0
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/TimeManager.asset b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/TimeManager.asset
new file mode 100644
index 0000000000..558a017e1f
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/TimeManager.asset
@@ -0,0 +1,9 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!5 &1
+TimeManager:
+ m_ObjectHideFlags: 0
+ Fixed Timestep: 0.02
+ Maximum Allowed Timestep: 0.33333334
+ m_TimeScale: 1
+ Maximum Particle Timestep: 0.03
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/UnityConnectSettings.asset b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/UnityConnectSettings.asset
new file mode 100644
index 0000000000..3da14d5baf
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/ProjectSettings/UnityConnectSettings.asset
@@ -0,0 +1,34 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!310 &1
+UnityConnectSettings:
+ m_ObjectHideFlags: 0
+ m_Enabled: 0
+ m_TestMode: 0
+ m_TestEventUrl:
+ m_TestConfigUrl:
+ m_TestInitMode: 0
+ CrashReportingSettings:
+ m_EventUrl: https://perf-events.cloud.unity3d.com/api/events/crashes
+ m_NativeEventUrl: https://perf-events.cloud.unity3d.com/symbolicate
+ m_Enabled: 0
+ m_CaptureEditorExceptions: 1
+ UnityPurchasingSettings:
+ m_Enabled: 0
+ m_TestMode: 0
+ UnityAnalyticsSettings:
+ m_Enabled: 0
+ m_InitializeOnStartup: 1
+ m_TestMode: 0
+ m_TestEventUrl:
+ m_TestConfigUrl:
+ UnityAdsSettings:
+ m_Enabled: 0
+ m_InitializeOnStartup: 1
+ m_TestMode: 0
+ m_IosGameId:
+ m_AndroidGameId:
+ m_GameIds: {}
+ m_GameId:
+ PerformanceReportingSettings:
+ m_Enabled: 0
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/README.md b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/README.md
new file mode 100644
index 0000000000..0b3813fccb
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/README.md
@@ -0,0 +1,24 @@
+# TF Lite Experimental Unity Plugin
+
+This directoryy contains an experimental sample Unity (2017) Plugin, based on
+the experimental TF Lite C API. The sample demonstrates running inference within
+Unity by way of a C# `Interpreter` wrapper.
+
+Note that the native TF Lite plugin(s) *must* be built before using the Unity
+Plugin, and placed in Assets/TensorFlowLite/SDK/Plugins/. For the editor (note
+that this has only been tested on Linux; the syntax may differ on Mac/Windows):
+
+```sh
+bazel build -c opt --cxxopt=--std=c++11 \
+ //tensorflow/contrib/lite/experimental/c:libtensorflowlite_c.so
+```
+
+and for Android:
+
+```sh
+bazel build -c opt --cxxopt=--std=c++11 \
+ --crosstool_top=//external:android/crosstool \
+ --host_crosstool_top=@bazel_tools//tools/cpp:toolchain \
+ --cpu=armeabi-v7a \
+ //tensorflow/contrib/lite/experimental/c:libtensorflowlite_c.so
+```
diff --git a/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/UnityPackageManager/manifest.json b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/UnityPackageManager/manifest.json
new file mode 100644
index 0000000000..526aca6057
--- /dev/null
+++ b/tensorflow/contrib/lite/experimental/examples/unity/TensorFlowLitePlugin/UnityPackageManager/manifest.json
@@ -0,0 +1,4 @@
+{
+ "dependencies": {
+ }
+}
diff --git a/tensorflow/contrib/lite/g3doc/custom_operators.md b/tensorflow/contrib/lite/g3doc/custom_operators.md
index 2296f5a064..d979353bb3 100644
--- a/tensorflow/contrib/lite/g3doc/custom_operators.md
+++ b/tensorflow/contrib/lite/g3doc/custom_operators.md
@@ -136,3 +136,39 @@ operations instead of a single operator.
6. Use TF_LITE_ENSURE(context, condition) to check for a specific condition.
Your code must not leave memory hanging when TF_LITE_ENSURE is done, i.e.,
these should be done before any resources are allocated that will leak.
+
+## Special TF Graph Attributes
+
+When Toco convertes a TF graph into TFLite format, it makes some assumption
+about custom operations that might be not correct. In this case, the generated
+graph can be not executable.
+
+It is possible to add aditional information about your custom op output to TF
+graph before it is converted. The following attributes are supported:
+
+- **_output_quantized** a boolean attribute, true if the operation outputs are
+ quantized
+- **_output_types** a list of types for output tensors
+- **_output_shapes** a list of shapes for output tensors
+
+### Setting the Attributes
+
+This is an example how the attributes can be set:
+
+```python
+frozen_graph_def = tf.graph_util.convert_variables_to_constants(...)
+for node in frozen_graph_def.node:
+ if node.op == 'sin':
+ node.attr['_output_types'].list.type.extend([
+ types_pb2.DT_FLOAT,
+ ])
+ node.attr['_output_shapes'].list.shape.extend([
+ tf.TensorShape([10]),
+ ])
+ node.attr['_output_quantized'].b = False
+tflite_model = tf.contrib.lite.toco_convert(
+ frozen_graph_def,...)
+```
+
+**Note:** After the attributes are set, the graph can not be executed by
+Tensorflow, therefore it should be done just before the conversion.
diff --git a/tensorflow/contrib/lite/g3doc/tf_ops_compatibility.md b/tensorflow/contrib/lite/g3doc/tf_ops_compatibility.md
index 0e8f4339fc..aa65ec9988 100644
--- a/tensorflow/contrib/lite/g3doc/tf_ops_compatibility.md
+++ b/tensorflow/contrib/lite/g3doc/tf_ops_compatibility.md
@@ -62,6 +62,7 @@ counterparts:
* [tf.nn.softmax](https://www.tensorflow.org/api_docs/python/tf/nn/softmax) -
*as long as tensors are 2D and axis is the last dimension*
* [tf.nn.top_k](https://www.tensorflow.org/api_docs/python/tf/nn/top_k)
+* [tf.one_hot](https://www.tensorflow.org/api_docs/python/tf/one_hot)
* [tf.pad](https://www.tensorflow.org/api_docs/python/tf/pad) - *as long as
mode and constant_values are not used*
* [tf.reduce_mean](https://www.tensorflow.org/api_docs/python/tf/reduce_mean) -
@@ -830,6 +831,18 @@ Outputs {
}
```
+**LOGICAL_OR**
+
+```
+Inputs {
+ 0: a list of tensors.
+ 1: a list of tensors.
+}
+Outputs {
+ 0: A tensor of logical_or output tensors.
+}
+```
+
And these are TensorFlow Lite operations that are present but not ready for
custom models yet:
diff --git a/tensorflow/contrib/lite/interpreter.cc b/tensorflow/contrib/lite/interpreter.cc
index 26fecceab0..e38597495d 100644
--- a/tensorflow/contrib/lite/interpreter.cc
+++ b/tensorflow/contrib/lite/interpreter.cc
@@ -43,10 +43,13 @@ namespace {
TfLiteStatus ReportOpError(TfLiteContext* context, const TfLiteNode& node,
const TfLiteRegistration& registration,
int node_index, const char* message) {
- context->ReportError(context, "Node number %d (%s) %s.\n", node_index,
- EnumNameBuiltinOperator(static_cast<BuiltinOperator>(
- registration.builtin_code)),
- message);
+ context->ReportError(
+ context, "Node number %d (%s) %s.\n", node_index,
+ registration.custom_name
+ ? registration.custom_name
+ : EnumNameBuiltinOperator(
+ static_cast<BuiltinOperator>(registration.builtin_code)),
+ message);
return kTfLiteError;
}
@@ -131,9 +134,7 @@ Interpreter::Interpreter(ErrorReporter* error_reporter)
context_.SetExternalContext = SetExternalContext;
// Invalid to call these these except from TfLiteDelegate
- SetForbiddenContextFunction(&context_.GetNodeAndRegistration);
- SetForbiddenContextFunction(&context_.ReplaceSubgraphsWithDelegateKernels);
- SetForbiddenContextFunction(&context_.GetExecutionPlan);
+ SwitchToKernelContext();
// Reserve some space for the tensors to avoid excessive resizing.
tensors_.reserve(kTensorsReservedCapacity);
@@ -278,8 +279,9 @@ TfLiteStatus Interpreter::ReplaceSubgraphsWithDelegateKernels(
int node_index;
TfLiteDelegateParams* params = CreateDelegateParams(delegate, subgraph);
- AddNodeWithParameters(subgraph.input_tensors, subgraph.output_tensors,
- nullptr, 0, params, &registration, &node_index);
+ TF_LITE_ENSURE_STATUS(AddNodeWithParameters(
+ subgraph.input_tensors, subgraph.output_tensors, nullptr, 0, params,
+ &registration, &node_index));
// Initialize the output tensors's delegate-related fields.
for (int tensor_index : subgraph.output_tensors) {
@@ -924,6 +926,19 @@ void Interpreter::SetNumThreads(int num_threads) {
}
}
+void Interpreter::SwitchToDelegateContext() {
+ context_.GetNodeAndRegistration = GetNodeAndRegistration;
+ context_.ReplaceSubgraphsWithDelegateKernels =
+ ReplaceSubgraphsWithDelegateKernels;
+ context_.GetExecutionPlan = GetExecutionPlan;
+}
+
+void Interpreter::SwitchToKernelContext() {
+ SetForbiddenContextFunction(&context_.GetNodeAndRegistration);
+ SetForbiddenContextFunction(&context_.ReplaceSubgraphsWithDelegateKernels);
+ SetForbiddenContextFunction(&context_.GetExecutionPlan);
+}
+
TfLiteStatus Interpreter::ModifyGraphWithDelegate(TfLiteDelegate* delegate,
bool allow_dynamic_tensors) {
if (!allow_dynamic_tensors) {
@@ -950,17 +965,12 @@ TfLiteStatus Interpreter::ModifyGraphWithDelegate(TfLiteDelegate* delegate,
// TODO(aselle): Consider if it is worth storing pointers to delegates.
// Setup additional context interface.
- context_.GetNodeAndRegistration = GetNodeAndRegistration;
- context_.ReplaceSubgraphsWithDelegateKernels =
- ReplaceSubgraphsWithDelegateKernels;
- context_.GetExecutionPlan = GetExecutionPlan;
+ SwitchToDelegateContext();
TfLiteStatus status = delegate->Prepare(&context_, delegate);
// Remove additional context info.
- SetForbiddenContextFunction(&context_.GetNodeAndRegistration);
- SetForbiddenContextFunction(&context_.ReplaceSubgraphsWithDelegateKernels);
- SetForbiddenContextFunction(&context_.GetExecutionPlan);
+ SwitchToKernelContext();
TF_LITE_ENSURE_OK(&context_, status);
diff --git a/tensorflow/contrib/lite/interpreter.h b/tensorflow/contrib/lite/interpreter.h
index bc608e2fce..be149a8cc0 100644
--- a/tensorflow/contrib/lite/interpreter.h
+++ b/tensorflow/contrib/lite/interpreter.h
@@ -111,7 +111,7 @@ class Interpreter {
// processing this model will be forwarded to the error_reporter object.
//
// Note, if error_reporter is nullptr, then a default StderrReporter is
- // used.
+ // used. Ownership of 'error_reporter' remains with the caller.
explicit Interpreter(ErrorReporter* error_reporter = DefaultErrorReporter());
~Interpreter();
@@ -416,6 +416,13 @@ class Interpreter {
private:
friend class InterpreterTest;
+ // Prevent 'context_' from accessing functions that are only available to
+ // delegated kernels.
+ void SwitchToKernelContext();
+
+ // Add delegate-only functions to 'context_'.
+ void SwitchToDelegateContext();
+
// Give 'op_reg' a chance to initialize itself using the contents of
// 'buffer'.
void* OpInit(const TfLiteRegistration& op_reg, const char* buffer,
@@ -502,6 +509,7 @@ class Interpreter {
// Update the execution graph to replace some of the nodes with stub
// nodes. Specifically any node index that has `nodes[index]==1` will be
// slated for replacement with a delegate kernel specified by registration.
+ // Ownership of 'nodes_to_replace' and 'delegate' remains with the caller.
// WARNING: This is an experimental interface that is subject to change.
TfLiteStatus ReplaceSubgraphsWithDelegateKernels(
TfLiteRegistration registration, const TfLiteIntArray* nodes_to_replace,
diff --git a/tensorflow/contrib/lite/interpreter_test.cc b/tensorflow/contrib/lite/interpreter_test.cc
index 10119903fe..2bf598bad7 100644
--- a/tensorflow/contrib/lite/interpreter_test.cc
+++ b/tensorflow/contrib/lite/interpreter_test.cc
@@ -647,18 +647,6 @@ TEST(BasicInterpreter, AllocateTwice) {
ASSERT_EQ(old_tensor1_ptr, interpreter.tensor(1)->data.raw);
}
-struct TestErrorReporter : public ErrorReporter {
- int Report(const char* format, va_list args) override {
- char buffer[1024];
- int size = vsnprintf(buffer, sizeof(buffer), format, args);
- all_reports += buffer;
- calls++;
- return size;
- }
- int calls = 0;
- std::string all_reports;
-};
-
TEST(BasicInterpreter, TestNullErrorReporter) {
TestErrorReporter reporter;
Interpreter interpreter;
@@ -668,8 +656,9 @@ TEST(BasicInterpreter, TestCustomErrorReporter) {
TestErrorReporter reporter;
Interpreter interpreter(&reporter);
ASSERT_NE(interpreter.Invoke(), kTfLiteOk);
- ASSERT_EQ(reporter.all_reports, "Invoke called on model that is not ready.");
- ASSERT_EQ(reporter.calls, 1);
+ ASSERT_EQ(reporter.error_messages(),
+ "Invoke called on model that is not ready.");
+ ASSERT_EQ(reporter.num_calls(), 1);
}
TEST(BasicInterpreter, TestUnsupportedDelegateFunctions) {
diff --git a/tensorflow/contrib/lite/java/src/main/native/nativeinterpreterwrapper_jni.cc b/tensorflow/contrib/lite/java/src/main/native/nativeinterpreterwrapper_jni.cc
index e2c1edd9af..fdcf00a0a0 100644
--- a/tensorflow/contrib/lite/java/src/main/native/nativeinterpreterwrapper_jni.cc
+++ b/tensorflow/contrib/lite/java/src/main/native/nativeinterpreterwrapper_jni.cc
@@ -152,10 +152,11 @@ Java_org_tensorflow_lite_NativeInterpreterWrapper_allocateTensors(
if (error_reporter == nullptr) return;
if (interpreter->AllocateTensors() != kTfLiteOk) {
- throwException(env, kNullPointerException,
- "Internal error: Cannot allocate memory for the interpreter:"
- " %s",
- error_reporter->CachedErrorMessage());
+ throwException(
+ env, kIllegalStateException,
+ "Internal error: Unexpected failure when preparing tensor allocations:"
+ " %s",
+ error_reporter->CachedErrorMessage());
}
}
@@ -336,10 +337,11 @@ Java_org_tensorflow_lite_NativeInterpreterWrapper_createInterpreter(
// allocates memory
status = interpreter->AllocateTensors();
if (status != kTfLiteOk) {
- throwException(env, kNullPointerException,
- "Internal error: Cannot allocate memory for the interpreter:"
- " %s",
- error_reporter->CachedErrorMessage());
+ throwException(
+ env, kIllegalStateException,
+ "Internal error: Unexpected failure when preparing tensor allocations:"
+ " %s",
+ error_reporter->CachedErrorMessage());
return 0;
}
return reinterpret_cast<jlong>(interpreter.release());
diff --git a/tensorflow/contrib/lite/kernels/BUILD b/tensorflow/contrib/lite/kernels/BUILD
index c224132cae..329c98f91e 100644
--- a/tensorflow/contrib/lite/kernels/BUILD
+++ b/tensorflow/contrib/lite/kernels/BUILD
@@ -170,12 +170,14 @@ cc_library(
"hashtable_lookup.cc",
"l2norm.cc",
"local_response_norm.cc",
+ "logical.cc",
"lsh_projection.cc",
"lstm.cc",
"maximum_minimum.cc",
"mfcc.cc",
"mul.cc",
"neg.cc",
+ "one_hot.cc",
"pack.cc",
"pad.cc",
"pooling.cc",
@@ -1171,6 +1173,33 @@ tf_cc_test(
],
)
+tf_cc_test(
+ name = "one_hot_test",
+ size = "small",
+ srcs = ["one_hot_test.cc"],
+ tags = ["tflite_not_portable_ios"],
+ deps = [
+ ":builtin_ops",
+ "//tensorflow/contrib/lite:framework",
+ "//tensorflow/contrib/lite/kernels:test_util",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
+tf_cc_test(
+ name = "logical_test",
+ size = "small",
+ srcs = ["logical_test.cc"],
+ tags = ["tflite_not_portable_ios"],
+ deps = [
+ ":builtin_ops",
+ "//tensorflow/contrib/lite:builtin_op_data",
+ "//tensorflow/contrib/lite:framework",
+ "//tensorflow/contrib/lite/kernels:test_util",
+ "@com_google_googletest//:gtest",
+ ],
+)
+
filegroup(
name = "all_files",
srcs = glob(
diff --git a/tensorflow/contrib/lite/kernels/internal/optimized/neon_tensor_utils.h b/tensorflow/contrib/lite/kernels/internal/optimized/neon_tensor_utils.h
index 45c9f65b64..63c89d1eee 100644
--- a/tensorflow/contrib/lite/kernels/internal/optimized/neon_tensor_utils.h
+++ b/tensorflow/contrib/lite/kernels/internal/optimized/neon_tensor_utils.h
@@ -115,10 +115,10 @@ void ClipVector(const float* vector, int v_size, float abs_limit,
}
void SymmetricQuantizeFloats(const float* values, const int size,
- int8_t* quantized_values, float* min, float* max,
- float* scaling_factor) {
- NEON_OR_PORTABLE(SymmetricQuantizeFloats, values, size, quantized_values, min,
- max, scaling_factor);
+ int8_t* quantized_values, float* min_value,
+ float* max_value, float* scaling_factor) {
+ NEON_OR_PORTABLE(SymmetricQuantizeFloats, values, size, quantized_values,
+ min_value, max_value, scaling_factor);
}
void VectorShiftLeft(float* vector, int v_size, float shift_value) {
diff --git a/tensorflow/contrib/lite/kernels/internal/optimized/tensor_utils_impl.h b/tensorflow/contrib/lite/kernels/internal/optimized/tensor_utils_impl.h
index db7926df9a..010b40b901 100644
--- a/tensorflow/contrib/lite/kernels/internal/optimized/tensor_utils_impl.h
+++ b/tensorflow/contrib/lite/kernels/internal/optimized/tensor_utils_impl.h
@@ -19,6 +19,10 @@ limitations under the License.
// structure.
#include "tensorflow/contrib/lite/builtin_op_data.h"
+#if defined(_MSC_VER)
+#define __restrict__ __restrict
+#endif
+
#ifndef USE_NEON
#if defined(__ARM_NEON__) || defined(__ARM_NEON)
#define USE_NEON
diff --git a/tensorflow/contrib/lite/kernels/internal/reference/portable_tensor_utils.cc b/tensorflow/contrib/lite/kernels/internal/reference/portable_tensor_utils.cc
index 7ead449ca8..6bd88b5596 100644
--- a/tensorflow/contrib/lite/kernels/internal/reference/portable_tensor_utils.cc
+++ b/tensorflow/contrib/lite/kernels/internal/reference/portable_tensor_utils.cc
@@ -14,6 +14,7 @@ limitations under the License.
==============================================================================*/
#include <stdlib.h>
#include <string.h>
+#include <algorithm>
#include "tensorflow/contrib/lite/builtin_op_data.h"
#include "tensorflow/contrib/lite/kernels/activation_functor.h"
@@ -38,14 +39,14 @@ bool PortableIsZeroVector(const float* vector, int v_size) {
void PortableSymmetricQuantizeFloats(const float* values, const int size,
int8_t* quantized_values,
- float* __restrict__ min,
- float* __restrict__ max,
+ float* __restrict__ min_value,
+ float* __restrict__ max_value,
float* __restrict__ scaling_factor) {
auto minmax = std::minmax_element(values, values + size);
- *min = *minmax.first;
- *max = *minmax.second;
+ *min_value = *minmax.first;
+ *max_value = *minmax.second;
const int kScale = 127;
- const float range = std::max(std::abs(*min), std::abs(*max));
+ const float range = std::max(std::abs(*min_value), std::abs(*max_value));
if (range == 0) {
memset(quantized_values, 0, size * sizeof(int8_t));
*scaling_factor = 1;
diff --git a/tensorflow/contrib/lite/kernels/internal/reference/portable_tensor_utils.h b/tensorflow/contrib/lite/kernels/internal/reference/portable_tensor_utils.h
index d3a4fa8507..a375aaffa6 100644
--- a/tensorflow/contrib/lite/kernels/internal/reference/portable_tensor_utils.h
+++ b/tensorflow/contrib/lite/kernels/internal/reference/portable_tensor_utils.h
@@ -19,6 +19,10 @@ limitations under the License.
// structure.
#include "tensorflow/contrib/lite/builtin_op_data.h"
+#if defined(_MSC_VER)
+#define __restrict__ __restrict
+#endif
+
namespace tflite {
namespace tensor_utils {
@@ -28,8 +32,8 @@ float PortableClip(float f, float abs_limit);
bool PortableIsZeroVector(const float* vector, int v_size);
void PortableSymmetricQuantizeFloats(const float* values, const int size,
- int8_t* quantized_values, float* min,
- float* max, float* scaling_factor);
+ int8_t* quantized_values, float* min_value,
+ float* max_value, float* scaling_factor);
// Multiply a matrix by a batch vector, and store results in a batch-size
// vector.
diff --git a/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h b/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h
index 31a54c2b62..714613b96e 100644
--- a/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h
+++ b/tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h
@@ -3284,7 +3284,8 @@ inline void SpaceToBatchND(const T* input_data, const Dims<4>& input_dims,
const Dims<4>& block_shape_dims,
const int32* paddings_data,
const Dims<4>& paddings_dims, T* output_data,
- const Dims<4>& output_dims) {
+ const Dims<4>& output_dims,
+ const int32_t pad_value) {
const int output_batch_size = ArraySize(output_dims, 3);
const int output_height = ArraySize(output_dims, 2);
const int output_width = ArraySize(output_dims, 1);
@@ -3309,7 +3310,7 @@ inline void SpaceToBatchND(const T* input_data, const Dims<4>& input_dims,
padding_top + input_height ||
out_w * block_shape_width + shift_w < padding_left ||
out_w * block_shape_width + shift_w >= padding_left + input_width) {
- memset(out, 0, depth * sizeof(T));
+ memset(out, pad_value, depth * sizeof(T));
} else {
const T* in =
input_data +
@@ -3325,6 +3326,17 @@ inline void SpaceToBatchND(const T* input_data, const Dims<4>& input_dims,
}
template <typename T>
+inline void SpaceToBatchND(const T* input_data, const Dims<4>& input_dims,
+ const int32* block_shape_data,
+ const Dims<4>& block_shape_dims,
+ const int32* paddings_data,
+ const Dims<4>& paddings_dims, T* output_data,
+ const Dims<4>& output_dims) {
+ SpaceToBatchND(input_data, input_dims, block_shape_data, block_shape_dims,
+ paddings_data, paddings_dims, output_data, output_dims, 0);
+}
+
+template <typename T>
inline void BatchToSpaceND(const T* input_data, const Dims<4>& input_dims,
const int32* block_shape_data,
const Dims<4>& block_shape_dims,
@@ -4243,6 +4255,38 @@ inline void BroadcastPow(const T* input1_data, const Dims<4>& input1_dims,
}
}
+inline void Logical(const bool* input1_data, const Dims<4>& input1_dims,
+ const bool* input2_data, const Dims<4>& input2_dims,
+ bool* output_data, const Dims<4>& output_dims,
+ const std::function<bool(bool, bool)>& func) {
+ const int flat_size = MatchingFlatSize(input1_dims, input2_dims, output_dims);
+ for (int i = 0; i < flat_size; ++i) {
+ output_data[i] = func(input1_data[i], input2_data[i]);
+ }
+}
+
+inline void BroadcastLogical(const bool* input1_data,
+ const Dims<4>& input1_dims,
+ const bool* input2_data,
+ const Dims<4>& input2_dims, bool* output_data,
+ const Dims<4>& output_dims,
+ const std::function<bool(bool, bool)>& func) {
+ NdArrayDesc<4> desc1;
+ NdArrayDesc<4> desc2;
+ NdArrayDescsForElementwiseBroadcast(input1_dims, input2_dims, &desc1, &desc2);
+ for (int b = 0; b < ArraySize(output_dims, 3); ++b) {
+ for (int y = 0; y < ArraySize(output_dims, 2); ++y) {
+ for (int x = 0; x < ArraySize(output_dims, 1); ++x) {
+ for (int c = 0; c < ArraySize(output_dims, 0); ++c) {
+ output_data[Offset(output_dims, c, x, y, b)] =
+ func(input1_data[SubscriptToIndex(desc1, c, x, y, b)],
+ input2_data[SubscriptToIndex(desc2, c, x, y, b)]);
+ }
+ }
+ }
+ }
+}
+
} // namespace reference_ops
} // namespace tflite
diff --git a/tensorflow/contrib/lite/kernels/internal/spectrogram.cc b/tensorflow/contrib/lite/kernels/internal/spectrogram.cc
index 4eddf7bf0a..20abcb7258 100644
--- a/tensorflow/contrib/lite/kernels/internal/spectrogram.cc
+++ b/tensorflow/contrib/lite/kernels/internal/spectrogram.cc
@@ -43,13 +43,13 @@ bool Spectrogram::Initialize(int window_length, int step_length) {
return Initialize(window, step_length);
}
-inline int Log2Floor(uint n) {
+inline int Log2Floor(uint32_t n) {
if (n == 0) return -1;
int log = 0;
- uint value = n;
+ uint32_t value = n;
for (int i = 4; i >= 0; --i) {
int shift = (1 << i);
- uint x = value >> shift;
+ uint32_t x = value >> shift;
if (x != 0) {
value = x;
log += shift;
@@ -58,7 +58,7 @@ inline int Log2Floor(uint n) {
return log;
}
-inline int Log2Ceiling(uint n) {
+inline int Log2Ceiling(uint32_t n) {
int floor = Log2Floor(n);
if (n == (n & ~(n - 1))) // zero or a power of two
return floor;
@@ -66,7 +66,7 @@ inline int Log2Ceiling(uint n) {
return floor + 1;
}
-inline uint NextPowerOfTwo(uint value) {
+inline uint32_t NextPowerOfTwo(uint32_t value) {
int exponent = Log2Ceiling(value);
// DCHECK_LT(exponent, std::numeric_limits<uint32>::digits);
return 1 << exponent;
diff --git a/tensorflow/contrib/lite/kernels/internal/tensor_utils.h b/tensorflow/contrib/lite/kernels/internal/tensor_utils.h
index 82f4503127..1ff8cfe39c 100644
--- a/tensorflow/contrib/lite/kernels/internal/tensor_utils.h
+++ b/tensorflow/contrib/lite/kernels/internal/tensor_utils.h
@@ -17,6 +17,10 @@ limitations under the License.
#include "tensorflow/contrib/lite/builtin_op_data.h"
+#if defined(_MSC_VER)
+#define __restrict__ __restrict
+#endif
+
namespace tflite {
namespace tensor_utils {
@@ -31,8 +35,8 @@ bool IsZeroVector(const float* vector, int v_size);
// It also outputs the range (min, max) of the floating point buffer, and the
// scaling factor used to quantize the values.
void SymmetricQuantizeFloats(const float* values, const int size,
- int8_t* quantized_values, float* min, float* max,
- float* scaling_factor);
+ int8_t* quantized_values, float* min_value,
+ float* max_value, float* scaling_factor);
// Multiplies a matrix by a "batched" vector (i.e. a matrix with a batch
// dimension composed by input vectors independent from each other). The result
diff --git a/tensorflow/contrib/lite/kernels/logical.cc b/tensorflow/contrib/lite/kernels/logical.cc
new file mode 100644
index 0000000000..3dc39bf79a
--- /dev/null
+++ b/tensorflow/contrib/lite/kernels/logical.cc
@@ -0,0 +1,121 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#include "tensorflow/contrib/lite/context.h"
+#include "tensorflow/contrib/lite/kernels/internal/reference/reference_ops.h"
+#include "tensorflow/contrib/lite/kernels/internal/tensor.h"
+#include "tensorflow/contrib/lite/kernels/kernel_util.h"
+#include "tensorflow/contrib/lite/kernels/op_macros.h"
+
+namespace tflite {
+namespace ops {
+namespace builtin {
+namespace logical {
+namespace {
+
+// Input/output tensor index.
+constexpr int kInputTensor1 = 0;
+constexpr int kInputTensor2 = 1;
+constexpr int kOutputTensor = 0;
+
+// Op data for logical op.
+struct OpData {
+ bool requires_broadcast;
+};
+
+void* Init(TfLiteContext* context, const char* buffer, size_t length) {
+ auto* data = new OpData;
+ data->requires_broadcast = false;
+ return data;
+}
+
+void Free(TfLiteContext* context, void* buffer) {
+ delete reinterpret_cast<OpData*>(buffer);
+}
+
+TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
+ TF_LITE_ENSURE_EQ(context, NumInputs(node), 2);
+ TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1);
+
+ // Reinterprete the opaque data provided by user.
+ OpData* data = reinterpret_cast<OpData*>(node->user_data);
+
+ const TfLiteTensor* input1 = GetInput(context, node, kInputTensor1);
+ const TfLiteTensor* input2 = GetInput(context, node, kInputTensor2);
+ TfLiteTensor* output = GetOutput(context, node, kOutputTensor);
+
+ TF_LITE_ENSURE_EQ(context, input1->type, input2->type);
+
+ const TfLiteType type = input1->type;
+ if (type != kTfLiteBool) {
+ context->ReportError(context, "Logical ops only support bool type.");
+ return kTfLiteError;
+ }
+ output->type = type;
+
+ data->requires_broadcast = !HaveSameShapes(input1, input2);
+
+ TfLiteIntArray* output_size = nullptr;
+ if (data->requires_broadcast) {
+ TF_LITE_ENSURE_OK(context, CalculateShapeForBroadcast(
+ context, input1, input2, &output_size));
+ } else {
+ output_size = TfLiteIntArrayCopy(input1->dims);
+ }
+
+ return context->ResizeTensor(context, output, output_size);
+}
+
+TfLiteStatus LogicalImpl(TfLiteContext* context, TfLiteNode* node,
+ const std::function<bool(bool, bool)>& func) {
+ OpData* data = reinterpret_cast<OpData*>(node->user_data);
+
+ const TfLiteTensor* input1 = GetInput(context, node, kInputTensor1);
+ const TfLiteTensor* input2 = GetInput(context, node, kInputTensor2);
+ TfLiteTensor* output = GetOutput(context, node, kOutputTensor);
+
+ if (data->requires_broadcast) {
+ reference_ops::BroadcastLogical(
+ GetTensorData<bool>(input1), GetTensorDims(input1),
+ GetTensorData<bool>(input2), GetTensorDims(input2),
+ GetTensorData<bool>(output), GetTensorDims(output), func);
+ } else {
+ reference_ops::Logical(GetTensorData<bool>(input1), GetTensorDims(input1),
+ GetTensorData<bool>(input2), GetTensorDims(input2),
+ GetTensorData<bool>(output), GetTensorDims(output),
+ func);
+ }
+
+ return kTfLiteOk;
+}
+
+TfLiteStatus LogicalOrEval(TfLiteContext* context, TfLiteNode* node) {
+ const auto logical_or_func = std::logical_or<bool>();
+ return LogicalImpl(context, node, logical_or_func);
+}
+
+} // namespace
+} // namespace logical
+
+TfLiteRegistration* Register_LOGICAL_OR() {
+ // Init, Free, Prepare, Eval are satisfying the Interface required by
+ // TfLiteRegistration.
+ static TfLiteRegistration r = {logical::Init, logical::Free, logical::Prepare,
+ logical::LogicalOrEval};
+ return &r;
+}
+
+} // namespace builtin
+} // namespace ops
+} // namespace tflite
diff --git a/tensorflow/contrib/lite/kernels/logical_test.cc b/tensorflow/contrib/lite/kernels/logical_test.cc
new file mode 100644
index 0000000000..382008245b
--- /dev/null
+++ b/tensorflow/contrib/lite/kernels/logical_test.cc
@@ -0,0 +1,87 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#include <gtest/gtest.h>
+#include "tensorflow/contrib/lite/interpreter.h"
+#include "tensorflow/contrib/lite/kernels/register.h"
+#include "tensorflow/contrib/lite/kernels/test_util.h"
+#include "tensorflow/contrib/lite/model.h"
+
+namespace tflite {
+namespace {
+
+using ::testing::ElementsAre;
+
+class LogicalOpModel : public SingleOpModel {
+ public:
+ LogicalOpModel(std::initializer_list<int> input1_shape,
+ std::initializer_list<int> input2_shape, BuiltinOperator op) {
+ input1_ = AddInput(TensorType_BOOL);
+ input2_ = AddInput(TensorType_BOOL);
+ output_ = AddOutput(TensorType_BOOL);
+ ConfigureBuiltinOp(op);
+ BuildInterpreter({input1_shape, input2_shape});
+ }
+
+ int input1() { return input1_; }
+ int input2() { return input2_; }
+
+ std::vector<bool> GetOutput() { return ExtractVector<bool>(output_); }
+ std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
+
+ private:
+ int input1_;
+ int input2_;
+ int output_;
+
+ void ConfigureBuiltinOp(BuiltinOperator op) {
+ switch (op) {
+ case BuiltinOperator_LOGICAL_OR: {
+ SetBuiltinOp(op, BuiltinOptions_LogicalOrOptions,
+ CreateLogicalOrOptions(builder_).Union());
+ break;
+ }
+ default: { FAIL() << "We shouldn't get here."; }
+ }
+ }
+};
+
+TEST(LogicalTest, LogicalOr) {
+ LogicalOpModel model({1, 1, 1, 4}, {1, 1, 1, 4}, BuiltinOperator_LOGICAL_OR);
+ model.PopulateTensor<bool>(model.input1(), {true, false, false, true});
+ model.PopulateTensor<bool>(model.input2(), {true, false, true, false});
+ model.Invoke();
+
+ EXPECT_THAT(model.GetOutput(), ElementsAre(true, false, true, true));
+ EXPECT_THAT(model.GetOutputShape(), ElementsAre(1, 1, 1, 4));
+}
+
+TEST(LogicalTest, BroadcastLogicalOr) {
+ LogicalOpModel model({1, 1, 1, 4}, {1, 1, 1, 1}, BuiltinOperator_LOGICAL_OR);
+ model.PopulateTensor<bool>(model.input1(), {true, false, false, true});
+ model.PopulateTensor<bool>(model.input2(), {false});
+ model.Invoke();
+
+ EXPECT_THAT(model.GetOutput(), ElementsAre(true, false, false, true));
+ EXPECT_THAT(model.GetOutputShape(), ElementsAre(1, 1, 1, 4));
+}
+
+} // namespace
+} // namespace tflite
+
+int main(int argc, char** argv) {
+ ::tflite::LogToStderr();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/tensorflow/contrib/lite/kernels/one_hot.cc b/tensorflow/contrib/lite/kernels/one_hot.cc
new file mode 100644
index 0000000000..9ff3dca932
--- /dev/null
+++ b/tensorflow/contrib/lite/kernels/one_hot.cc
@@ -0,0 +1,199 @@
+/* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#include "tensorflow/contrib/lite/builtin_op_data.h"
+#include "tensorflow/contrib/lite/context.h"
+#include "tensorflow/contrib/lite/kernels/internal/tensor.h"
+#include "tensorflow/contrib/lite/kernels/kernel_util.h"
+#include "tensorflow/contrib/lite/kernels/op_macros.h"
+
+namespace tflite {
+namespace ops {
+namespace builtin {
+namespace one_hot {
+
+constexpr int kIndicesTensor = 0;
+constexpr int kDepthTensor = 1;
+constexpr int kOnValueTensor = 2;
+constexpr int kOffValueTensor = 3;
+constexpr int kOutputTensor = 0;
+
+// Convenience utility for destructuring a node into the appropriate tensors and
+// data for the op. Note that this destructuring is quite cheap, so we can avoid
+// allocating op-specific, persistent data on the heap.
+struct OneHotContext {
+ OneHotContext(TfLiteContext* context, TfLiteNode* node) {
+ indices = GetInput(context, node, kIndicesTensor);
+ depth = GetInput(context, node, kDepthTensor);
+ on_value = GetInput(context, node, kOnValueTensor);
+ off_value = GetInput(context, node, kOffValueTensor);
+ output = GetOutput(context, node, kOutputTensor);
+
+ const auto* params =
+ reinterpret_cast<TfLiteOneHotParams*>(node->builtin_data);
+ const int indices_dims = indices->dims->size;
+ axis = (params->axis == -1) ? indices_dims : params->axis;
+ output_dims = indices_dims + 1;
+ dtype = on_value->type;
+ }
+
+ const TfLiteTensor* indices;
+ const TfLiteTensor* depth;
+ const TfLiteTensor* on_value;
+ const TfLiteTensor* off_value;
+ TfLiteTensor* output;
+ int axis;
+ int output_dims;
+ TfLiteType dtype;
+};
+
+template <typename T, typename TI>
+void OneHotComputeImpl(const OneHotContext& op_context) {
+ // prefix_dim_size == # of elements before the axis
+ // depth == # of elements per axis
+ // suffix_dim_size == # of elements after the axis
+ int prefix_dim_size = 1;
+ for (int i = 0; i < op_context.axis; ++i) {
+ prefix_dim_size *= op_context.indices->dims->data[i];
+ }
+ const int suffix_dim_size = NumElements(op_context.indices) / prefix_dim_size;
+ const int depth = *op_context.depth->data.i32;
+
+ const T on_value = *GetTensorData<T>(op_context.on_value);
+ const T off_value = *GetTensorData<T>(op_context.off_value);
+
+ // View the indices as a matrix of size:
+ // prefix_dim_size x suffix_dim_size
+ // View the output as a matrix of size:
+ // prefix_dim_size x depth x suffix_dim_size
+ // Then the output is:
+ // output(i, j, k) == (indices(i, k) == j) ? on : off
+ T* output = GetTensorData<T>(op_context.output);
+ const TI* indices = GetTensorData<TI>(op_context.indices);
+ for (int i = 0; i < prefix_dim_size; ++i) {
+ for (int j = 0; j < depth; ++j) {
+ for (int k = 0; k < suffix_dim_size; ++k, ++output) {
+ *output = static_cast<int>(indices[i * suffix_dim_size + k]) == j
+ ? on_value
+ : off_value;
+ }
+ }
+ }
+}
+
+template <typename T>
+void OneHotCompute(const OneHotContext& op_context) {
+ if (op_context.indices->type == kTfLiteInt64) {
+ OneHotComputeImpl<T, int64_t>(op_context);
+ } else {
+ OneHotComputeImpl<T, int>(op_context);
+ }
+}
+
+TfLiteStatus ResizeOutputTensor(TfLiteContext* context,
+ const OneHotContext& op_context) {
+ TF_LITE_ENSURE(context, *op_context.depth->data.i32 >= 0);
+ TfLiteIntArray* output_size = TfLiteIntArrayCreate(op_context.output_dims);
+ for (int i = 0; i < op_context.output_dims; ++i) {
+ if (i < op_context.axis) {
+ output_size->data[i] = op_context.indices->dims->data[i];
+ } else if (i == op_context.axis) {
+ output_size->data[i] = *op_context.depth->data.i32;
+ } else {
+ output_size->data[i] = op_context.indices->dims->data[i - 1];
+ }
+ }
+ return context->ResizeTensor(context, op_context.output, output_size);
+}
+
+TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
+ TF_LITE_ENSURE_EQ(context, NumInputs(node), 4);
+ TF_LITE_ENSURE_EQ(context, NumOutputs(node), 1);
+
+ OneHotContext op_context{context, node};
+ switch (op_context.dtype) {
+ // TODO(b/111744875): Support uint8 and quantization.
+ case kTfLiteFloat32:
+ case kTfLiteInt16:
+ case kTfLiteInt32:
+ case kTfLiteInt64:
+ case kTfLiteBool:
+ op_context.output->type = op_context.dtype;
+ break;
+ default:
+ context->ReportError(context, "Unknown output data type: %d",
+ op_context.dtype);
+ return kTfLiteError;
+ }
+
+ TF_LITE_ENSURE(context, op_context.indices->type == kTfLiteInt32 ||
+ op_context.indices->type == kTfLiteInt64);
+ TF_LITE_ENSURE(context, op_context.axis >= 0 &&
+ op_context.axis < op_context.output_dims);
+ TF_LITE_ENSURE_EQ(context, NumElements(op_context.depth), 1);
+ TF_LITE_ENSURE_EQ(context, NumElements(op_context.on_value), 1);
+ TF_LITE_ENSURE_EQ(context, NumElements(op_context.off_value), 1);
+ TF_LITE_ENSURE_EQ(context, op_context.on_value->type, op_context.dtype);
+ TF_LITE_ENSURE_EQ(context, op_context.off_value->type, op_context.dtype);
+
+ if (!IsConstantTensor(op_context.depth)) {
+ SetTensorToDynamic(op_context.output);
+ return kTfLiteOk;
+ }
+
+ return ResizeOutputTensor(context, op_context);
+}
+
+TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
+ OneHotContext op_context{context, node};
+
+ if (IsDynamicTensor(op_context.output)) {
+ ResizeOutputTensor(context, op_context);
+ }
+
+ switch (op_context.output->type) {
+ case kTfLiteFloat32:
+ OneHotCompute<float>(op_context);
+ break;
+ case kTfLiteInt32:
+ OneHotCompute<int>(op_context);
+ break;
+ case kTfLiteInt64:
+ OneHotCompute<int64_t>(op_context);
+ break;
+ case kTfLiteBool:
+ OneHotCompute<bool>(op_context);
+ break;
+ default:
+ return kTfLiteError;
+ }
+
+ return kTfLiteOk;
+}
+
+} // namespace one_hot
+
+TfLiteRegistration* Register_ONE_HOT() {
+ static TfLiteRegistration r = {
+ nullptr,
+ nullptr,
+ one_hot::Prepare,
+ one_hot::Eval,
+ };
+ return &r;
+}
+
+} // namespace builtin
+} // namespace ops
+} // namespace tflite
diff --git a/tensorflow/contrib/lite/kernels/one_hot_test.cc b/tensorflow/contrib/lite/kernels/one_hot_test.cc
new file mode 100644
index 0000000000..6b604ec7a7
--- /dev/null
+++ b/tensorflow/contrib/lite/kernels/one_hot_test.cc
@@ -0,0 +1,182 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include <initializer_list>
+
+#include <gtest/gtest.h>
+#include "tensorflow/contrib/lite/interpreter.h"
+#include "tensorflow/contrib/lite/kernels/register.h"
+#include "tensorflow/contrib/lite/kernels/test_util.h"
+#include "tensorflow/contrib/lite/model.h"
+
+namespace tflite {
+namespace {
+
+using ::testing::ElementsAreArray;
+
+template <typename T>
+class OneHotOpModel : public SingleOpModel {
+ public:
+ OneHotOpModel(std::initializer_list<int> input_shape, int depth_value,
+ TensorType dtype, int axis = -1, T on_value = 1,
+ T off_value = 0, TensorType indices_type = TensorType_INT32) {
+ indices_ = AddInput(indices_type);
+ int depth = AddInput(TensorType_INT32);
+ int on = AddInput(dtype);
+ int off = AddInput(dtype);
+ output_ = AddOutput(dtype);
+ SetBuiltinOp(BuiltinOperator_ONE_HOT, BuiltinOptions_OneHotOptions,
+ CreateOneHotOptions(builder_, axis).Union());
+ BuildInterpreter({input_shape});
+
+ PopulateTensor<int>(depth, {depth_value});
+ PopulateTensor<T>(on, {on_value});
+ PopulateTensor<T>(off, {off_value});
+ }
+
+ template <typename TI>
+ void SetIndices(std::initializer_list<TI> data) {
+ PopulateTensor<TI>(indices_, data);
+ }
+
+ TfLiteStatus InvokeWithResult() { return interpreter_->Invoke(); }
+
+ int32_t GetOutputSize() { return GetTensorSize(output_); }
+ std::vector<T> GetOutput() { return ExtractVector<T>(output_); }
+ std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
+
+ private:
+ int indices_;
+ int output_;
+};
+
+TEST(OneHotOpTest, BasicFloat) {
+ const int depth = 3;
+ OneHotOpModel<float> model({3}, depth, TensorType_FLOAT32);
+ model.SetIndices({0, 1, 2});
+ model.Invoke();
+
+ EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({3, 3}));
+ EXPECT_THAT(model.GetOutput(),
+ ElementsAreArray({1.f, 0.f, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 1.f}));
+}
+
+TEST(OneHotOpTest, BasicInt) {
+ const int depth = 3;
+ OneHotOpModel<int> model({3}, depth, TensorType_INT32);
+ model.SetIndices({0, 1, 2});
+ model.Invoke();
+
+ EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({3, 3}));
+ EXPECT_THAT(model.GetOutput(), ElementsAreArray({1, 0, 0, 0, 1, 0, 0, 0, 1}));
+}
+
+TEST(OneHotOpTest, BasicBool) {
+ const int depth = 3;
+ OneHotOpModel<bool> model({3}, depth, TensorType_BOOL);
+ model.SetIndices({0, 1, 2});
+ model.Invoke();
+
+ EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({3, 3}));
+ EXPECT_THAT(model.GetOutput(),
+ ElementsAreArray({true, false, false, false, true, false, false,
+ false, true}));
+}
+
+TEST(OneHotOpTest, SmallDepth) {
+ const int depth = 1;
+ OneHotOpModel<int> model({3}, depth, TensorType_INT32);
+ model.SetIndices({0, 1, 2});
+ model.Invoke();
+
+ EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({3, 1}));
+ EXPECT_THAT(model.GetOutput(), ElementsAreArray({1, 0, 0}));
+}
+
+TEST(OneHotOpTest, BigDepth) {
+ const int depth = 4;
+ OneHotOpModel<int> model({2}, depth, TensorType_INT32);
+ model.SetIndices({0, 1});
+ model.Invoke();
+
+ EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 4}));
+ EXPECT_THAT(model.GetOutput(), ElementsAreArray({1, 0, 0, 0, 0, 1, 0, 0}));
+}
+
+TEST(OneHotOpTest, OnOffValues) {
+ const int depth = 3;
+ const int axis = -1;
+ const int on = 5;
+ const int off = 0;
+ OneHotOpModel<int> model({4}, depth, TensorType_INT32, axis, on, off);
+ model.SetIndices({0, 2, -1, 1});
+ model.Invoke();
+
+ EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({4, 3}));
+ EXPECT_THAT(model.GetOutput(),
+ ElementsAreArray({5, 0, 0, 0, 0, 5, 0, 0, 0, 0, 5, 0}));
+}
+
+TEST(OneHotOpTest, ZeroAxis) {
+ const int depth = 3;
+ const int axis = 0;
+ const int on = 5;
+ const int off = 0;
+ OneHotOpModel<int> model({4}, depth, TensorType_INT32, axis, on, off);
+ model.SetIndices({0, 2, -1, 1});
+ model.Invoke();
+
+ EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({3, 4}));
+ EXPECT_THAT(model.GetOutput(),
+ ElementsAreArray({5, 0, 0, 0, 0, 0, 0, 5, 0, 5, 0, 0}));
+}
+
+TEST(OneHotOpTest, MultiDimensionalIndices) {
+ const int depth = 3;
+ const int axis = -1;
+ const float on = 2;
+ const float off = 0;
+ OneHotOpModel<float> model({2, 2}, depth, TensorType_FLOAT32, axis, on, off);
+ model.SetIndices({0, 2, 1, -1});
+ model.Invoke();
+
+ EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({2, 2, 3}));
+ EXPECT_THAT(model.GetOutput(),
+ ElementsAreArray({2, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0}));
+}
+
+TEST(OneHotOpTest, Int64Indices) {
+ const int depth = 3;
+ const int axis = -1;
+ const int on = 1;
+ const int off = 0;
+ OneHotOpModel<int> model({3}, depth, TensorType_INT32, axis, on, off,
+ TensorType_INT64);
+ std::initializer_list<int64_t> indices = {0, 1, 2};
+ model.SetIndices(indices);
+ model.Invoke();
+
+ EXPECT_THAT(model.GetOutputShape(), ElementsAreArray({3, 3}));
+ EXPECT_THAT(model.GetOutput(), ElementsAreArray({1, 0, 0, 0, 1, 0, 0, 0, 1}));
+}
+
+} // namespace
+} // namespace tflite
+
+int main(int argc, char** argv) {
+ ::tflite::LogToStderr();
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/tensorflow/contrib/lite/kernels/register.cc b/tensorflow/contrib/lite/kernels/register.cc
index 0b70bed308..e632728841 100644
--- a/tensorflow/contrib/lite/kernels/register.cc
+++ b/tensorflow/contrib/lite/kernels/register.cc
@@ -107,6 +107,8 @@ TfLiteRegistration* Register_SHAPE();
TfLiteRegistration* Register_POW();
TfLiteRegistration* Register_FAKE_QUANT();
TfLiteRegistration* Register_PACK();
+TfLiteRegistration* Register_ONE_HOT();
+TfLiteRegistration* Register_LOGICAL_OR();
BuiltinOpResolver::BuiltinOpResolver() {
AddBuiltin(BuiltinOperator_RELU, Register_RELU());
@@ -197,6 +199,8 @@ BuiltinOpResolver::BuiltinOpResolver() {
AddBuiltin(BuiltinOperator_POW, Register_POW());
AddBuiltin(BuiltinOperator_FAKE_QUANT, Register_FAKE_QUANT(), 1, 2);
AddBuiltin(BuiltinOperator_PACK, Register_PACK());
+ AddBuiltin(BuiltinOperator_ONE_HOT, Register_ONE_HOT());
+ AddBuiltin(BuiltinOperator_LOGICAL_OR, Register_LOGICAL_OR());
// TODO(andrewharp, ahentz): Move these somewhere more appropriate so that
// custom ops aren't always included by default.
diff --git a/tensorflow/contrib/lite/kernels/reshape.cc b/tensorflow/contrib/lite/kernels/reshape.cc
index 99ecc16093..49ba0571e2 100644
--- a/tensorflow/contrib/lite/kernels/reshape.cc
+++ b/tensorflow/contrib/lite/kernels/reshape.cc
@@ -37,10 +37,7 @@ TfLiteStatus ResizeOutput(TfLiteContext* context, TfLiteNode* node,
// special -1 value, meaning it will be calculated automatically based on the
// input. Here we calculate what that dimension should be so that the number
// of output elements in the same as the number of input elements.
- int num_input_elements = 1;
- for (int i = 0; i < NumDimensions(input); ++i) {
- num_input_elements *= SizeOfDimension(input, i);
- }
+ int num_input_elements = NumElements(input);
int num_output_elements = 1;
int stretch_dim = -1;
@@ -96,9 +93,15 @@ TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) {
}
// The function is returned above this line if the shape tensor is usable.
// Now fallback to the shape parameter in `TfLiteReshapeParams`.
-
- TfLiteIntArray* output_shape = TfLiteIntArrayCreate(params->num_dimensions);
- for (int i = 0; i < params->num_dimensions; ++i) {
+ int num_dimensions = params->num_dimensions;
+ if (num_dimensions == 1 && params->shape[0] == 0) {
+ // Legacy tflite models use a shape parameter of [0] to indicate scalars,
+ // so adjust accordingly. TODO(b/111614235): Allow zero-sized buffers during
+ // toco conversion.
+ num_dimensions = 0;
+ }
+ TfLiteIntArray* output_shape = TfLiteIntArrayCreate(num_dimensions);
+ for (int i = 0; i < num_dimensions; ++i) {
output_shape->data[i] = params->shape[i];
}
return ResizeOutput(context, node, output_shape);
diff --git a/tensorflow/contrib/lite/kernels/reshape_test.cc b/tensorflow/contrib/lite/kernels/reshape_test.cc
index aecbd0399f..52d71350d3 100644
--- a/tensorflow/contrib/lite/kernels/reshape_test.cc
+++ b/tensorflow/contrib/lite/kernels/reshape_test.cc
@@ -22,18 +22,27 @@ namespace tflite {
namespace {
using ::testing::ElementsAreArray;
+using ::testing::IsEmpty;
class ReshapeOpModel : public SingleOpModel {
public:
ReshapeOpModel(std::initializer_list<int> input_shape,
- std::initializer_list<int> new_shape) {
+ std::initializer_list<int> new_shape,
+ bool use_shape_input_tensor = false) {
input_ = AddInput(TensorType_FLOAT32);
output_ = AddOutput(TensorType_FLOAT32);
+ int shape_input_tensor =
+ use_shape_input_tensor ? AddInput(TensorType_INT32) : -1;
SetBuiltinOp(
BuiltinOperator_RESHAPE, BuiltinOptions_ReshapeOptions,
CreateReshapeOptions(builder_, builder_.CreateVector<int>(new_shape))
.Union());
- BuildInterpreter({input_shape});
+ if (use_shape_input_tensor) {
+ BuildInterpreter({input_shape, GetShape(shape_input_tensor)});
+ PopulateTensor<int>(shape_input_tensor, new_shape);
+ } else {
+ BuildInterpreter({input_shape});
+ }
}
void SetInput(std::initializer_list<float> data) {
@@ -71,6 +80,14 @@ TEST(ReshapeOpTest, SimpleTest) {
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 2, 2}));
}
+TEST(ReshapeOpTest, ShapeTensorInput) {
+ ReshapeOpModel m({1, 2, 4, 1}, {2, 2, 2}, /*use_shape_input_tensor=*/true);
+ m.SetInput({1, 2, 3, 4, 5, 6, 7, 8});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutput(), ElementsAreArray({1, 2, 3, 4, 5, 6, 7, 8}));
+ EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 2, 2}));
+}
+
TEST(ReshapeOpTest, WithStretchDimension) {
ReshapeOpModel m({1, 2, 4, 1}, {2, 1, -1});
m.SetInput({1, 2, 3, 4, 5, 6, 7, 8});
@@ -79,6 +96,22 @@ TEST(ReshapeOpTest, WithStretchDimension) {
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({2, 1, 4}));
}
+TEST(ReshapeOpTest, ScalarOutput) {
+ ReshapeOpModel m({1}, {});
+ m.SetInput({3});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutput(), ElementsAreArray({3}));
+ EXPECT_THAT(m.GetOutputShape(), IsEmpty());
+}
+
+TEST(ReshapeOpTest, LegacyScalarOutput) {
+ ReshapeOpModel m({1}, {0});
+ m.SetInput({3});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutput(), ElementsAreArray({3}));
+ EXPECT_THAT(m.GetOutputShape(), IsEmpty());
+}
+
} // namespace
} // namespace tflite
diff --git a/tensorflow/contrib/lite/kernels/resize_bilinear_test.cc b/tensorflow/contrib/lite/kernels/resize_bilinear_test.cc
index 10caffea03..f4289105f7 100644
--- a/tensorflow/contrib/lite/kernels/resize_bilinear_test.cc
+++ b/tensorflow/contrib/lite/kernels/resize_bilinear_test.cc
@@ -247,7 +247,7 @@ TEST(ResizeBilinearOpTest, TwoDimensionalResizeWithTwoBatches8Bit) {
3, 6, //
9, 12, //
4, 10, //
- 10, 16 //
+ 12, 16 //
});
m.SetSize({3, 3});
m.Invoke();
@@ -256,8 +256,8 @@ TEST(ResizeBilinearOpTest, TwoDimensionalResizeWithTwoBatches8Bit) {
7, 9, 10, //
9, 11, 12, //
4, 8, 10, //
- 8, 12, 14, //
- 10, 13, 16, //
+ 9, 12, 14, //
+ 12, 14, 16, //
})));
ResizeBilinearOpModel const_m({TensorType_UINT8, {2, 2, 2, 1}}, {3, 3});
@@ -265,7 +265,7 @@ TEST(ResizeBilinearOpTest, TwoDimensionalResizeWithTwoBatches8Bit) {
3, 6, //
9, 12, //
4, 10, //
- 10, 16 //
+ 12, 16 //
});
const_m.Invoke();
EXPECT_THAT(const_m.GetOutput<uint8>(), ElementsAreArray(ArrayFloatNear({
@@ -273,35 +273,35 @@ TEST(ResizeBilinearOpTest, TwoDimensionalResizeWithTwoBatches8Bit) {
7, 9, 10, //
9, 11, 12, //
4, 8, 10, //
- 8, 12, 14, //
- 10, 13, 16, //
+ 9, 12, 14, //
+ 12, 14, 16, //
})));
}
TEST(ResizeBilinearOpTest, ThreeDimensionalResize8Bit) {
ResizeBilinearOpModel m({TensorType_UINT8, {1, 2, 2, 2}});
m.SetInput<uint8>({
- 3, 4, 6, 10, //
- 9, 10, 12, 16, //
+ 3, 4, 6, 10, //
+ 10, 12, 14, 16, //
});
m.SetSize({3, 3});
m.Invoke();
EXPECT_THAT(m.GetOutput<uint8>(), ElementsAreArray(ArrayFloatNear({
- 3, 4, 5, 8, 6, 10, //
- 7, 8, 9, 12, 10, 14, //
- 9, 10, 11, 13, 12, 16, //
+ 3, 4, 5, 8, 6, 10, //
+ 7, 9, 10, 12, 11, 14, //
+ 10, 12, 12, 14, 14, 16, //
})));
ResizeBilinearOpModel const_m({TensorType_UINT8, {1, 2, 2, 2}}, {3, 3});
const_m.SetInput<uint8>({
- 3, 4, 6, 10, //
- 9, 10, 12, 16, //
+ 3, 4, 6, 10, //
+ 10, 12, 14, 16, //
});
const_m.Invoke();
EXPECT_THAT(const_m.GetOutput<uint8>(), ElementsAreArray(ArrayFloatNear({
- 3, 4, 5, 8, 6, 10, //
- 7, 8, 9, 12, 10, 14, //
- 9, 10, 11, 13, 12, 16, //
+ 3, 4, 5, 8, 6, 10, //
+ 7, 9, 10, 12, 11, 14, //
+ 10, 12, 12, 14, 14, 16, //
})));
}
} // namespace
diff --git a/tensorflow/contrib/lite/kernels/space_to_batch_nd.cc b/tensorflow/contrib/lite/kernels/space_to_batch_nd.cc
index c9269599e5..03079f1c3b 100644
--- a/tensorflow/contrib/lite/kernels/space_to_batch_nd.cc
+++ b/tensorflow/contrib/lite/kernels/space_to_batch_nd.cc
@@ -113,7 +113,7 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
TF_LITE_ENSURE_OK(context, ResizeOutputTensor(context, &op_context));
}
-#define TF_LITE_SPACE_TO_BATCH_ND(type, scalar) \
+#define TF_LITE_SPACE_TO_BATCH_ND(type, scalar, pad_value) \
type::SpaceToBatchND(GetTensorData<scalar>(op_context.input), \
GetTensorDims(op_context.input), \
GetTensorData<int32_t>(op_context.block_shape), \
@@ -121,34 +121,36 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
GetTensorData<int32_t>(op_context.paddings), \
GetTensorDims(op_context.paddings), \
GetTensorData<scalar>(op_context.output), \
- GetTensorDims(op_context.output))
+ GetTensorDims(op_context.output), pad_value)
switch (op_context.input->type) { // Already know in/out types are same.
case kTfLiteFloat32:
if (kernel_type == kReference) {
- TF_LITE_SPACE_TO_BATCH_ND(reference_ops, float);
+ TF_LITE_SPACE_TO_BATCH_ND(reference_ops, float, 0);
} else {
- TF_LITE_SPACE_TO_BATCH_ND(optimized_ops, float);
+ TF_LITE_SPACE_TO_BATCH_ND(optimized_ops, float, 0);
}
break;
case kTfLiteUInt8:
if (kernel_type == kReference) {
- TF_LITE_SPACE_TO_BATCH_ND(reference_ops, uint8_t);
+ TF_LITE_SPACE_TO_BATCH_ND(reference_ops, uint8_t,
+ op_context.output->params.zero_point);
} else {
- TF_LITE_SPACE_TO_BATCH_ND(optimized_ops, uint8_t);
+ TF_LITE_SPACE_TO_BATCH_ND(optimized_ops, uint8_t,
+ op_context.output->params.zero_point);
}
break;
case kTfLiteInt32:
if (kernel_type == kReference) {
- TF_LITE_SPACE_TO_BATCH_ND(reference_ops, int32_t);
+ TF_LITE_SPACE_TO_BATCH_ND(reference_ops, int32_t, 0);
} else {
- TF_LITE_SPACE_TO_BATCH_ND(optimized_ops, int32_t);
+ TF_LITE_SPACE_TO_BATCH_ND(optimized_ops, int32_t, 0);
}
break;
case kTfLiteInt64:
if (kernel_type == kReference) {
- TF_LITE_SPACE_TO_BATCH_ND(reference_ops, int64_t);
+ TF_LITE_SPACE_TO_BATCH_ND(reference_ops, int64_t, 0);
} else {
- TF_LITE_SPACE_TO_BATCH_ND(optimized_ops, int64_t);
+ TF_LITE_SPACE_TO_BATCH_ND(optimized_ops, int64_t, 0);
}
break;
default:
diff --git a/tensorflow/contrib/lite/kernels/space_to_batch_nd_test.cc b/tensorflow/contrib/lite/kernels/space_to_batch_nd_test.cc
index 92a4a037d5..5756573629 100644
--- a/tensorflow/contrib/lite/kernels/space_to_batch_nd_test.cc
+++ b/tensorflow/contrib/lite/kernels/space_to_batch_nd_test.cc
@@ -23,6 +23,7 @@ namespace tflite {
namespace {
using ::testing::ElementsAreArray;
+using ::testing::Matcher;
class SpaceToBatchNDOpModel : public SingleOpModel {
public:
@@ -30,6 +31,10 @@ class SpaceToBatchNDOpModel : public SingleOpModel {
PopulateTensor<float>(input_, data);
}
+ void SetQuantizedInput(std::initializer_list<float> data) {
+ QuantizeAndPopulate<uint8_t>(input_, data);
+ }
+
void SetBlockShape(std::initializer_list<int> data) {
PopulateTensor<int>(block_shape_, data);
}
@@ -41,6 +46,11 @@ class SpaceToBatchNDOpModel : public SingleOpModel {
std::vector<float> GetOutput() { return ExtractVector<float>(output_); }
std::vector<int> GetOutputShape() { return GetTensorShape(output_); }
+ std::vector<float> GetDequantizedOutput() {
+ return Dequantize<uint8_t>(ExtractVector<uint8_t>(output_),
+ GetScale(output_), GetZeroPoint(output_));
+ }
+
protected:
int input_;
int block_shape_;
@@ -56,18 +66,19 @@ class SpaceToBatchNDOpModel : public SingleOpModel {
// m.Invoke();
class SpaceToBatchNDOpConstModel : public SpaceToBatchNDOpModel {
public:
- SpaceToBatchNDOpConstModel(std::initializer_list<int> input_shape,
+ SpaceToBatchNDOpConstModel(const TensorData& input,
std::initializer_list<int> block_shape,
- std::initializer_list<int> paddings) {
- input_ = AddInput(TensorType_FLOAT32);
+ std::initializer_list<int> paddings,
+ const TensorData& output) {
+ input_ = AddInput(input);
block_shape_ = AddConstInput(TensorType_INT32, block_shape, {2});
paddings_ = AddConstInput(TensorType_INT32, paddings, {2, 2});
- output_ = AddOutput(TensorType_FLOAT32);
+ output_ = AddOutput(output);
SetBuiltinOp(BuiltinOperator_SPACE_TO_BATCH_ND,
BuiltinOptions_SpaceToBatchNDOptions,
CreateSpaceToBatchNDOptions(builder_).Union());
- BuildInterpreter({input_shape});
+ BuildInterpreter({input.shape});
}
};
@@ -81,26 +92,30 @@ class SpaceToBatchNDOpConstModel : public SpaceToBatchNDOpModel {
// m.Invoke();
class SpaceToBatchNDOpDynamicModel : public SpaceToBatchNDOpModel {
public:
- SpaceToBatchNDOpDynamicModel(std::initializer_list<int> input_shape) {
- input_ = AddInput(TensorType_FLOAT32);
+ SpaceToBatchNDOpDynamicModel(const TensorData& input,
+ const TensorData& output) {
+ input_ = AddInput(input);
block_shape_ = AddInput(TensorType_INT32);
paddings_ = AddInput(TensorType_INT32);
- output_ = AddOutput(TensorType_FLOAT32);
+ output_ = AddOutput(output);
SetBuiltinOp(BuiltinOperator_SPACE_TO_BATCH_ND,
BuiltinOptions_SpaceToBatchNDOptions,
CreateSpaceToBatchNDOptions(builder_).Union());
- BuildInterpreter({input_shape, {2}, {2, 2}});
+ BuildInterpreter({input.shape, {2}, {2, 2}});
}
};
TEST(SpaceToBatchNDOpTest, InvalidShapeTest) {
- EXPECT_DEATH(SpaceToBatchNDOpConstModel({1, 3, 3, 1}, {2, 2}, {0, 0, 0, 0}),
- "Cannot allocate tensors");
+ EXPECT_DEATH(
+ SpaceToBatchNDOpConstModel({TensorType_FLOAT32, {1, 3, 3, 1}}, {2, 2},
+ {0, 0, 0, 0}, {TensorType_FLOAT32}),
+ "Cannot allocate tensors");
}
TEST(SpaceToBatchNDOpTest, SimpleConstTest) {
- SpaceToBatchNDOpConstModel m({1, 4, 4, 1}, {2, 2}, {0, 0, 0, 0});
+ SpaceToBatchNDOpConstModel m({TensorType_FLOAT32, {1, 4, 4, 1}}, {2, 2},
+ {0, 0, 0, 0}, {TensorType_FLOAT32});
m.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
m.Invoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({4, 2, 2, 1}));
@@ -109,7 +124,8 @@ TEST(SpaceToBatchNDOpTest, SimpleConstTest) {
}
TEST(SpaceToBatchNDOpTest, SimpleDynamicTest) {
- SpaceToBatchNDOpDynamicModel m({1, 4, 4, 1});
+ SpaceToBatchNDOpDynamicModel m({TensorType_FLOAT32, {1, 4, 4, 1}},
+ {TensorType_FLOAT32});
m.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
m.SetBlockShape({2, 2});
m.SetPaddings({0, 0, 0, 0});
@@ -120,7 +136,8 @@ TEST(SpaceToBatchNDOpTest, SimpleDynamicTest) {
}
TEST(SpaceToBatchNDOpTest, MultipleInputBatchesConstTest) {
- SpaceToBatchNDOpConstModel m({2, 2, 4, 1}, {2, 2}, {0, 0, 0, 0});
+ SpaceToBatchNDOpConstModel m({TensorType_FLOAT32, {2, 2, 4, 1}}, {2, 2},
+ {0, 0, 0, 0}, {TensorType_FLOAT32});
m.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
m.Invoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({8, 1, 2, 1}));
@@ -129,7 +146,8 @@ TEST(SpaceToBatchNDOpTest, MultipleInputBatchesConstTest) {
}
TEST(SpaceToBatchNDOpTest, MultipleInputBatchesDynamicTest) {
- SpaceToBatchNDOpDynamicModel m({2, 2, 4, 1});
+ SpaceToBatchNDOpDynamicModel m({TensorType_FLOAT32, {2, 2, 4, 1}},
+ {TensorType_FLOAT32});
m.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
m.SetBlockShape({2, 2});
m.SetPaddings({0, 0, 0, 0});
@@ -140,7 +158,8 @@ TEST(SpaceToBatchNDOpTest, MultipleInputBatchesDynamicTest) {
}
TEST(SpaceToBatchNDOpTest, SimplePaddingConstTest) {
- SpaceToBatchNDOpConstModel m({1, 5, 2, 1}, {3, 2}, {1, 0, 2, 0});
+ SpaceToBatchNDOpConstModel m({TensorType_FLOAT32, {1, 5, 2, 1}}, {3, 2},
+ {1, 0, 2, 0}, {TensorType_FLOAT32});
m.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
m.Invoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({6, 2, 2, 1}));
@@ -151,7 +170,8 @@ TEST(SpaceToBatchNDOpTest, SimplePaddingConstTest) {
}
TEST(SpaceToBatchNDOpTest, SimplePaddingDynamicTest) {
- SpaceToBatchNDOpDynamicModel m({1, 5, 2, 1});
+ SpaceToBatchNDOpDynamicModel m({TensorType_FLOAT32, {1, 5, 2, 1}},
+ {TensorType_FLOAT32});
m.SetInput({1, 2, 3, 4, 5, 6, 7, 8, 9, 10});
m.SetBlockShape({3, 2});
m.SetPaddings({1, 0, 2, 0});
@@ -164,7 +184,8 @@ TEST(SpaceToBatchNDOpTest, SimplePaddingDynamicTest) {
}
TEST(SpaceToBatchNDOpTest, ComplexPaddingConstTest) {
- SpaceToBatchNDOpConstModel m({1, 4, 2, 1}, {3, 2}, {1, 1, 2, 4});
+ SpaceToBatchNDOpConstModel m({TensorType_FLOAT32, {1, 4, 2, 1}}, {3, 2},
+ {1, 1, 2, 4}, {TensorType_FLOAT32});
m.SetInput({1, 2, 3, 4, 5, 6, 7, 8});
m.Invoke();
EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({6, 2, 4, 1}));
@@ -176,7 +197,8 @@ TEST(SpaceToBatchNDOpTest, ComplexPaddingConstTest) {
}
TEST(SpaceToBatchNDOpTest, ComplexPaddingDynamicTest) {
- SpaceToBatchNDOpDynamicModel m({1, 4, 2, 1});
+ SpaceToBatchNDOpDynamicModel m({TensorType_FLOAT32, {1, 4, 2, 1}},
+ {TensorType_FLOAT32});
m.SetInput({1, 2, 3, 4, 5, 6, 7, 8});
m.SetBlockShape({3, 2});
m.SetPaddings({1, 1, 2, 4});
@@ -189,6 +211,88 @@ TEST(SpaceToBatchNDOpTest, ComplexPaddingDynamicTest) {
}));
}
+class QuantizedSpaceToBatchNDOpTest : public ::testing::Test {
+ protected:
+ std::vector<Matcher<float>> DequantizedArrayNear(
+ const std::vector<float>& values, const float min, const float max) {
+ const float quantization_tolerance = (max - min) / 255.0;
+ return ArrayFloatNear(values, quantization_tolerance);
+ }
+};
+
+TEST_F(QuantizedSpaceToBatchNDOpTest, ZeroNotInQuantizationRange) {
+ // The test_util and actual quantization code currently ensure that the range
+ // must include zero, but if that ever changes, this test will catch it.
+ EXPECT_DEATH(SpaceToBatchNDOpConstModel m(
+ {TensorType_UINT8, {1, 2, 2, 1}, 1.0, 2.0}, {4, 2},
+ {0, 0, 1, 1, 1, 1, 0, 0}, {TensorType_UINT8, {}, 1.0, 2.0}),
+ ".*Check failed: f_min <= 0.*");
+}
+
+TEST_F(QuantizedSpaceToBatchNDOpTest, SimplePaddingConstTest) {
+ SpaceToBatchNDOpConstModel m({TensorType_UINT8, {1, 5, 2, 1}, -1.0, 1.0},
+ {3, 2}, {1, 0, 2, 0},
+ {TensorType_UINT8, {}, -1.0, 1.0});
+ m.SetQuantizedInput({-0.1, 0.2, -0.3, 0.4, -0.5, 0.6, -0.7, 0.8, -0.9, 0.1});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({6, 2, 2, 1}));
+ EXPECT_THAT(m.GetDequantizedOutput(),
+ ElementsAreArray(DequantizedArrayNear(
+ {0, 0, 0, -0.5, 0, 0, 0, 0.6, 0, -0.1, 0, -0.7,
+ 0, 0.2, 0, 0.8, 0, -0.3, 0, -0.9, 0, 0.4, 0, 0.1},
+ -1.0, 1.0)));
+}
+
+TEST_F(QuantizedSpaceToBatchNDOpTest, SimplePaddingDynamicTest) {
+ SpaceToBatchNDOpDynamicModel m({TensorType_UINT8, {1, 5, 2, 1}, -1.0, 1.0},
+ {TensorType_UINT8, {}, -1.0, 1.0});
+ m.SetQuantizedInput({-0.1, 0.2, -0.3, 0.4, -0.5, 0.6, -0.7, 0.8, -0.9, 0.1});
+ m.SetBlockShape({3, 2});
+ m.SetPaddings({1, 0, 2, 0});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({6, 2, 2, 1}));
+ EXPECT_THAT(m.GetDequantizedOutput(),
+ ElementsAreArray(DequantizedArrayNear(
+ {0, 0, 0, -0.5, 0, 0, 0, 0.6, 0, -0.1, 0, -0.7,
+ 0, 0.2, 0, 0.8, 0, -0.3, 0, -0.9, 0, 0.4, 0, 0.1},
+ -1.0, 1.0)));
+}
+
+TEST_F(QuantizedSpaceToBatchNDOpTest, ComplexPaddingConstTest) {
+ SpaceToBatchNDOpConstModel m({TensorType_UINT8, {1, 4, 2, 1}, -1.0, 1.0},
+ {3, 2}, {1, 1, 2, 4},
+ {TensorType_UINT8, {}, -1.0, 1.0});
+ m.SetQuantizedInput({-0.1, 0.2, -0.3, 0.4, -0.5, 0.6, -0.7, 0.8});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({6, 2, 4, 1}));
+ EXPECT_THAT(m.GetDequantizedOutput(),
+ ElementsAreArray(DequantizedArrayNear(
+ {
+ 0, 0, 0, 0, 0, -0.5, 0, 0, 0, 0, 0, 0, 0, 0.6, 0, 0,
+ 0, -0.1, 0, 0, 0, -0.7, 0, 0, 0, 0.2, 0, 0, 0, 0.8, 0, 0,
+ 0, -0.3, 0, 0, 0, 0, 0, 0, 0, 0.4, 0, 0, 0, 0, 0, 0,
+ },
+ -1.0, 1.0)));
+}
+
+TEST_F(QuantizedSpaceToBatchNDOpTest, ComplexPaddingDynamicTest) {
+ SpaceToBatchNDOpDynamicModel m({TensorType_UINT8, {1, 4, 2, 1}, -1.0, 1.0},
+ {TensorType_UINT8, {}, -1.0, 1.0});
+ m.SetQuantizedInput({-0.1, 0.2, -0.3, 0.4, -0.5, 0.6, -0.7, 0.8});
+ m.SetBlockShape({3, 2});
+ m.SetPaddings({1, 1, 2, 4});
+ m.Invoke();
+ EXPECT_THAT(m.GetOutputShape(), ElementsAreArray({6, 2, 4, 1}));
+ EXPECT_THAT(m.GetDequantizedOutput(),
+ ElementsAreArray(DequantizedArrayNear(
+ {
+ 0, 0, 0, 0, 0, -0.5, 0, 0, 0, 0, 0, 0, 0, 0.6, 0, 0,
+ 0, -0.1, 0, 0, 0, -0.7, 0, 0, 0, 0.2, 0, 0, 0, 0.8, 0, 0,
+ 0, -0.3, 0, 0, 0, 0, 0, 0, 0, 0.4, 0, 0, 0, 0, 0, 0,
+ },
+ -1.0, 1.0)));
+}
+
} // namespace
} // namespace tflite
diff --git a/tensorflow/contrib/lite/model.cc b/tensorflow/contrib/lite/model.cc
index c6869feb16..5814cddc5b 100644
--- a/tensorflow/contrib/lite/model.cc
+++ b/tensorflow/contrib/lite/model.cc
@@ -730,6 +730,14 @@ TfLiteStatus ParseOpData(const Operator* op, BuiltinOperator op_type,
*builtin_data = static_cast<void*>(params);
break;
}
+ case BuiltinOperator_ONE_HOT: {
+ auto* params = MallocPOD<TfLiteOneHotParams>();
+ if (auto* schema_params = op->builtin_options_as_OneHotOptions()) {
+ params->axis = schema_params->axis();
+ }
+ *builtin_data = static_cast<void*>(params);
+ break;
+ }
// Below are the ops with no builtin_data strcture.
case BuiltinOperator_BATCH_TO_SPACE_ND:
diff --git a/tensorflow/contrib/lite/model_test.cc b/tensorflow/contrib/lite/model_test.cc
index edfdec9315..df4f60d4ad 100644
--- a/tensorflow/contrib/lite/model_test.cc
+++ b/tensorflow/contrib/lite/model_test.cc
@@ -241,14 +241,6 @@ TEST(BasicFlatBufferModel, TestWithNullVerifier) {
"tensorflow/contrib/lite/testdata/test_model.bin", nullptr));
}
-struct TestErrorReporter : public ErrorReporter {
- int Report(const char* format, va_list args) override {
- calls++;
- return 0;
- }
- int calls = 0;
-};
-
// This makes sure the ErrorReporter is marshalled from FlatBufferModel to
// the Interpreter.
TEST(BasicFlatBufferModel, TestCustomErrorReporter) {
@@ -262,7 +254,7 @@ TEST(BasicFlatBufferModel, TestCustomErrorReporter) {
TrivialResolver resolver;
InterpreterBuilder(*model, resolver)(&interpreter);
ASSERT_NE(interpreter->Invoke(), kTfLiteOk);
- ASSERT_EQ(reporter.calls, 1);
+ ASSERT_EQ(reporter.num_calls(), 1);
}
// This makes sure the ErrorReporter is marshalled from FlatBufferModel to
diff --git a/tensorflow/contrib/lite/nnapi_delegate.cc b/tensorflow/contrib/lite/nnapi_delegate.cc
index 551e8ed320..1c06b29deb 100644
--- a/tensorflow/contrib/lite/nnapi_delegate.cc
+++ b/tensorflow/contrib/lite/nnapi_delegate.cc
@@ -623,6 +623,7 @@ TfLiteStatus AddOpsAndParams(
case tflite::BuiltinOperator_FAKE_QUANT:
case tflite::BuiltinOperator_PACK:
case tflite::BuiltinOperator_LOGICAL_OR:
+ case tflite::BuiltinOperator_ONE_HOT:
logError("Op code %d is currently not delegated to NNAPI", builtin);
return kTfLiteError;
break;
diff --git a/tensorflow/contrib/lite/profiling/time.cc b/tensorflow/contrib/lite/profiling/time.cc
index 446660bb74..875ddb02bc 100644
--- a/tensorflow/contrib/lite/profiling/time.cc
+++ b/tensorflow/contrib/lite/profiling/time.cc
@@ -14,16 +14,34 @@ limitations under the License.
==============================================================================*/
#include "tensorflow/contrib/lite/profiling/time.h"
+#if defined(_MSC_VER)
+#include <chrono> // NOLINT(build/c++11)
+#else
#include <sys/time.h>
+#endif
namespace tflite {
namespace profiling {
namespace time {
+
+#if defined(_MSC_VER)
+
+uint64_t NowMicros() {
+ return std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count();
+}
+
+#else
+
uint64_t NowMicros() {
struct timeval tv;
gettimeofday(&tv, nullptr);
return static_cast<uint64_t>(tv.tv_sec) * 1000000 + tv.tv_usec;
}
+
+#endif // defined(_MSC_VER)
+
} // namespace time
} // namespace profiling
} // namespace tflite
diff --git a/tensorflow/contrib/lite/schema/schema.fbs b/tensorflow/contrib/lite/schema/schema.fbs
index a285bf9919..8ed98ddaf4 100644
--- a/tensorflow/contrib/lite/schema/schema.fbs
+++ b/tensorflow/contrib/lite/schema/schema.fbs
@@ -166,6 +166,7 @@ enum BuiltinOperator : byte {
REDUCE_MAX = 82,
PACK = 83,
LOGICAL_OR = 84,
+ ONE_HOT = 85,
}
// Options for the builtin operators.
@@ -230,6 +231,7 @@ union BuiltinOptions {
FakeQuantOptions,
PackOptions,
LogicalOrOptions,
+ OneHotOptions,
}
enum Padding : byte { SAME, VALID }
@@ -549,6 +551,10 @@ table PackOptions {
table LogicalOrOptions {
}
+table OneHotOptions {
+ axis:int;
+}
+
// An OperatorCode can be an enum value (BuiltinOperator) if the operator is a
// builtin, or a string if the operator is custom.
table OperatorCode {
diff --git a/tensorflow/contrib/lite/schema/schema_generated.h b/tensorflow/contrib/lite/schema/schema_generated.h
index 8c1d6d6a36..4402f89b85 100755
--- a/tensorflow/contrib/lite/schema/schema_generated.h
+++ b/tensorflow/contrib/lite/schema/schema_generated.h
@@ -211,6 +211,9 @@ struct PackOptionsT;
struct LogicalOrOptions;
struct LogicalOrOptionsT;
+struct OneHotOptions;
+struct OneHotOptionsT;
+
struct OperatorCode;
struct OperatorCodeT;
@@ -361,11 +364,12 @@ enum BuiltinOperator {
BuiltinOperator_REDUCE_MAX = 82,
BuiltinOperator_PACK = 83,
BuiltinOperator_LOGICAL_OR = 84,
+ BuiltinOperator_ONE_HOT = 85,
BuiltinOperator_MIN = BuiltinOperator_ADD,
- BuiltinOperator_MAX = BuiltinOperator_LOGICAL_OR
+ BuiltinOperator_MAX = BuiltinOperator_ONE_HOT
};
-inline BuiltinOperator (&EnumValuesBuiltinOperator())[84] {
+inline BuiltinOperator (&EnumValuesBuiltinOperator())[85] {
static BuiltinOperator values[] = {
BuiltinOperator_ADD,
BuiltinOperator_AVERAGE_POOL_2D,
@@ -450,7 +454,8 @@ inline BuiltinOperator (&EnumValuesBuiltinOperator())[84] {
BuiltinOperator_REDUCE_PROD,
BuiltinOperator_REDUCE_MAX,
BuiltinOperator_PACK,
- BuiltinOperator_LOGICAL_OR
+ BuiltinOperator_LOGICAL_OR,
+ BuiltinOperator_ONE_HOT
};
return values;
}
@@ -542,6 +547,7 @@ inline const char **EnumNamesBuiltinOperator() {
"REDUCE_MAX",
"PACK",
"LOGICAL_OR",
+ "ONE_HOT",
nullptr
};
return names;
@@ -614,11 +620,12 @@ enum BuiltinOptions {
BuiltinOptions_FakeQuantOptions = 58,
BuiltinOptions_PackOptions = 59,
BuiltinOptions_LogicalOrOptions = 60,
+ BuiltinOptions_OneHotOptions = 61,
BuiltinOptions_MIN = BuiltinOptions_NONE,
- BuiltinOptions_MAX = BuiltinOptions_LogicalOrOptions
+ BuiltinOptions_MAX = BuiltinOptions_OneHotOptions
};
-inline BuiltinOptions (&EnumValuesBuiltinOptions())[61] {
+inline BuiltinOptions (&EnumValuesBuiltinOptions())[62] {
static BuiltinOptions values[] = {
BuiltinOptions_NONE,
BuiltinOptions_Conv2DOptions,
@@ -680,7 +687,8 @@ inline BuiltinOptions (&EnumValuesBuiltinOptions())[61] {
BuiltinOptions_ArgMinOptions,
BuiltinOptions_FakeQuantOptions,
BuiltinOptions_PackOptions,
- BuiltinOptions_LogicalOrOptions
+ BuiltinOptions_LogicalOrOptions,
+ BuiltinOptions_OneHotOptions
};
return values;
}
@@ -748,6 +756,7 @@ inline const char **EnumNamesBuiltinOptions() {
"FakeQuantOptions",
"PackOptions",
"LogicalOrOptions",
+ "OneHotOptions",
nullptr
};
return names;
@@ -1002,6 +1011,10 @@ template<> struct BuiltinOptionsTraits<LogicalOrOptions> {
static const BuiltinOptions enum_value = BuiltinOptions_LogicalOrOptions;
};
+template<> struct BuiltinOptionsTraits<OneHotOptions> {
+ static const BuiltinOptions enum_value = BuiltinOptions_OneHotOptions;
+};
+
struct BuiltinOptionsUnion {
BuiltinOptions type;
void *value;
@@ -1513,6 +1526,14 @@ struct BuiltinOptionsUnion {
return type == BuiltinOptions_LogicalOrOptions ?
reinterpret_cast<const LogicalOrOptionsT *>(value) : nullptr;
}
+ OneHotOptionsT *AsOneHotOptions() {
+ return type == BuiltinOptions_OneHotOptions ?
+ reinterpret_cast<OneHotOptionsT *>(value) : nullptr;
+ }
+ const OneHotOptionsT *AsOneHotOptions() const {
+ return type == BuiltinOptions_OneHotOptions ?
+ reinterpret_cast<const OneHotOptionsT *>(value) : nullptr;
+ }
};
bool VerifyBuiltinOptions(flatbuffers::Verifier &verifier, const void *obj, BuiltinOptions type);
@@ -5452,6 +5473,60 @@ inline flatbuffers::Offset<LogicalOrOptions> CreateLogicalOrOptions(
flatbuffers::Offset<LogicalOrOptions> CreateLogicalOrOptions(flatbuffers::FlatBufferBuilder &_fbb, const LogicalOrOptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr);
+struct OneHotOptionsT : public flatbuffers::NativeTable {
+ typedef OneHotOptions TableType;
+ int32_t axis;
+ OneHotOptionsT()
+ : axis(0) {
+ }
+};
+
+struct OneHotOptions FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
+ typedef OneHotOptionsT NativeTableType;
+ enum {
+ VT_AXIS = 4
+ };
+ int32_t axis() const {
+ return GetField<int32_t>(VT_AXIS, 0);
+ }
+ bool Verify(flatbuffers::Verifier &verifier) const {
+ return VerifyTableStart(verifier) &&
+ VerifyField<int32_t>(verifier, VT_AXIS) &&
+ verifier.EndTable();
+ }
+ OneHotOptionsT *UnPack(const flatbuffers::resolver_function_t *_resolver = nullptr) const;
+ void UnPackTo(OneHotOptionsT *_o, const flatbuffers::resolver_function_t *_resolver = nullptr) const;
+ static flatbuffers::Offset<OneHotOptions> Pack(flatbuffers::FlatBufferBuilder &_fbb, const OneHotOptionsT* _o, const flatbuffers::rehasher_function_t *_rehasher = nullptr);
+};
+
+struct OneHotOptionsBuilder {
+ flatbuffers::FlatBufferBuilder &fbb_;
+ flatbuffers::uoffset_t start_;
+ void add_axis(int32_t axis) {
+ fbb_.AddElement<int32_t>(OneHotOptions::VT_AXIS, axis, 0);
+ }
+ explicit OneHotOptionsBuilder(flatbuffers::FlatBufferBuilder &_fbb)
+ : fbb_(_fbb) {
+ start_ = fbb_.StartTable();
+ }
+ OneHotOptionsBuilder &operator=(const OneHotOptionsBuilder &);
+ flatbuffers::Offset<OneHotOptions> Finish() {
+ const auto end = fbb_.EndTable(start_);
+ auto o = flatbuffers::Offset<OneHotOptions>(end);
+ return o;
+ }
+};
+
+inline flatbuffers::Offset<OneHotOptions> CreateOneHotOptions(
+ flatbuffers::FlatBufferBuilder &_fbb,
+ int32_t axis = 0) {
+ OneHotOptionsBuilder builder_(_fbb);
+ builder_.add_axis(axis);
+ return builder_.Finish();
+}
+
+flatbuffers::Offset<OneHotOptions> CreateOneHotOptions(flatbuffers::FlatBufferBuilder &_fbb, const OneHotOptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher = nullptr);
+
struct OperatorCodeT : public flatbuffers::NativeTable {
typedef OperatorCode TableType;
BuiltinOperator builtin_code;
@@ -5765,6 +5840,9 @@ struct Operator FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
const LogicalOrOptions *builtin_options_as_LogicalOrOptions() const {
return builtin_options_type() == BuiltinOptions_LogicalOrOptions ? static_cast<const LogicalOrOptions *>(builtin_options()) : nullptr;
}
+ const OneHotOptions *builtin_options_as_OneHotOptions() const {
+ return builtin_options_type() == BuiltinOptions_OneHotOptions ? static_cast<const OneHotOptions *>(builtin_options()) : nullptr;
+ }
const flatbuffers::Vector<uint8_t> *custom_options() const {
return GetPointer<const flatbuffers::Vector<uint8_t> *>(VT_CUSTOM_OPTIONS);
}
@@ -6036,6 +6114,10 @@ template<> inline const LogicalOrOptions *Operator::builtin_options_as<LogicalOr
return builtin_options_as_LogicalOrOptions();
}
+template<> inline const OneHotOptions *Operator::builtin_options_as<OneHotOptions>() const {
+ return builtin_options_as_OneHotOptions();
+}
+
struct OperatorBuilder {
flatbuffers::FlatBufferBuilder &fbb_;
flatbuffers::uoffset_t start_;
@@ -8151,6 +8233,32 @@ inline flatbuffers::Offset<LogicalOrOptions> CreateLogicalOrOptions(flatbuffers:
_fbb);
}
+inline OneHotOptionsT *OneHotOptions::UnPack(const flatbuffers::resolver_function_t *_resolver) const {
+ auto _o = new OneHotOptionsT();
+ UnPackTo(_o, _resolver);
+ return _o;
+}
+
+inline void OneHotOptions::UnPackTo(OneHotOptionsT *_o, const flatbuffers::resolver_function_t *_resolver) const {
+ (void)_o;
+ (void)_resolver;
+ { auto _e = axis(); _o->axis = _e; };
+}
+
+inline flatbuffers::Offset<OneHotOptions> OneHotOptions::Pack(flatbuffers::FlatBufferBuilder &_fbb, const OneHotOptionsT* _o, const flatbuffers::rehasher_function_t *_rehasher) {
+ return CreateOneHotOptions(_fbb, _o, _rehasher);
+}
+
+inline flatbuffers::Offset<OneHotOptions> CreateOneHotOptions(flatbuffers::FlatBufferBuilder &_fbb, const OneHotOptionsT *_o, const flatbuffers::rehasher_function_t *_rehasher) {
+ (void)_rehasher;
+ (void)_o;
+ struct _VectorArgs { flatbuffers::FlatBufferBuilder *__fbb; const OneHotOptionsT* __o; const flatbuffers::rehasher_function_t *__rehasher; } _va = { &_fbb, _o, _rehasher}; (void)_va;
+ auto _axis = _o->axis;
+ return tflite::CreateOneHotOptions(
+ _fbb,
+ _axis);
+}
+
inline OperatorCodeT *OperatorCode::UnPack(const flatbuffers::resolver_function_t *_resolver) const {
auto _o = new OperatorCodeT();
UnPackTo(_o, _resolver);
@@ -8580,6 +8688,10 @@ inline bool VerifyBuiltinOptions(flatbuffers::Verifier &verifier, const void *ob
auto ptr = reinterpret_cast<const LogicalOrOptions *>(obj);
return verifier.VerifyTable(ptr);
}
+ case BuiltinOptions_OneHotOptions: {
+ auto ptr = reinterpret_cast<const OneHotOptions *>(obj);
+ return verifier.VerifyTable(ptr);
+ }
default: return false;
}
}
@@ -8838,6 +8950,10 @@ inline void *BuiltinOptionsUnion::UnPack(const void *obj, BuiltinOptions type, c
auto ptr = reinterpret_cast<const LogicalOrOptions *>(obj);
return ptr->UnPack(resolver);
}
+ case BuiltinOptions_OneHotOptions: {
+ auto ptr = reinterpret_cast<const OneHotOptions *>(obj);
+ return ptr->UnPack(resolver);
+ }
default: return nullptr;
}
}
@@ -9084,6 +9200,10 @@ inline flatbuffers::Offset<void> BuiltinOptionsUnion::Pack(flatbuffers::FlatBuff
auto ptr = reinterpret_cast<const LogicalOrOptionsT *>(value);
return CreateLogicalOrOptions(_fbb, ptr, _rehasher).Union();
}
+ case BuiltinOptions_OneHotOptions: {
+ auto ptr = reinterpret_cast<const OneHotOptionsT *>(value);
+ return CreateOneHotOptions(_fbb, ptr, _rehasher).Union();
+ }
default: return 0;
}
}
@@ -9330,6 +9450,10 @@ inline BuiltinOptionsUnion::BuiltinOptionsUnion(const BuiltinOptionsUnion &u) FL
value = new LogicalOrOptionsT(*reinterpret_cast<LogicalOrOptionsT *>(u.value));
break;
}
+ case BuiltinOptions_OneHotOptions: {
+ value = new OneHotOptionsT(*reinterpret_cast<OneHotOptionsT *>(u.value));
+ break;
+ }
default:
break;
}
@@ -9637,6 +9761,11 @@ inline void BuiltinOptionsUnion::Reset() {
delete ptr;
break;
}
+ case BuiltinOptions_OneHotOptions: {
+ auto ptr = reinterpret_cast<OneHotOptionsT *>(value);
+ delete ptr;
+ break;
+ }
default: break;
}
value = nullptr;
diff --git a/tensorflow/contrib/lite/simple_memory_arena.cc b/tensorflow/contrib/lite/simple_memory_arena.cc
index 24593d2a67..cd0f1f7c17 100644
--- a/tensorflow/contrib/lite/simple_memory_arena.cc
+++ b/tensorflow/contrib/lite/simple_memory_arena.cc
@@ -15,6 +15,7 @@ limitations under the License.
#include "tensorflow/contrib/lite/simple_memory_arena.h"
+#include <algorithm>
#include <cstring>
#include <limits>
#include <vector>
diff --git a/tensorflow/contrib/lite/testing/BUILD b/tensorflow/contrib/lite/testing/BUILD
index 6c7f494e9b..a788d41ba7 100644
--- a/tensorflow/contrib/lite/testing/BUILD
+++ b/tensorflow/contrib/lite/testing/BUILD
@@ -210,6 +210,10 @@ cc_library(
cc_library(
name = "util",
hdrs = ["util.h"],
+ deps = [
+ "//tensorflow/contrib/lite:framework",
+ "//tensorflow/contrib/lite:string",
+ ],
)
cc_test(
@@ -253,6 +257,7 @@ cc_test(
srcs = ["tf_driver_test.cc"],
data = ["//tensorflow/contrib/lite:testdata/multi_add.pb"],
tags = [
+ "no_oss",
"tflite_not_portable",
],
deps = [
@@ -279,6 +284,7 @@ cc_test(
size = "small",
srcs = ["generate_testspec_test.cc"],
tags = [
+ "no_oss",
"tflite_not_portable",
],
deps = [
@@ -335,6 +341,7 @@ tf_cc_test(
],
tags = [
"no_cuda_on_cpu_tap",
+ "no_oss", # needs test data
"tflite_not_portable",
],
deps = [
diff --git a/tensorflow/contrib/lite/testing/generate_examples.py b/tensorflow/contrib/lite/testing/generate_examples.py
index 41ece94237..6d03c0fd9e 100644
--- a/tensorflow/contrib/lite/testing/generate_examples.py
+++ b/tensorflow/contrib/lite/testing/generate_examples.py
@@ -104,6 +104,8 @@ KNOWN_BUGS = {
r"div.*int32": "72051395",
# No support for SplitV
r"split.*num_or_size_splits=\[2,2\]": "73377559",
+ # Scalar constants don't work.
+ r"constant.*shape=\[\]": "109811500",
}
@@ -229,6 +231,7 @@ _TF_TYPE_INFO = {
tf.int32: (np.int32, "INT32"),
tf.uint8: (np.uint8, "QUANTIZED_UINT8"),
tf.int64: (np.int64, "INT64"),
+ tf.bool: (np.bool, "BOOL"),
}
@@ -242,7 +245,10 @@ def create_tensor_data(dtype, shape, min_value=-100, max_value=100):
value = (max_value-min_value)*np.random.random_sample(shape)+min_value
elif dtype in (tf.int32, tf.uint8, tf.int64):
value = np.random.randint(min_value, max_value+1, shape)
- return value.astype(dtype)
+ elif dtype == tf.bool:
+ value = np.random.choice([True, False], size=shape)
+ return np.dtype(dtype).type(value) if np.isscalar(value) else value.astype(
+ dtype)
def create_scalar_data(dtype, min_value=-100, max_value=100):
@@ -479,7 +485,7 @@ def make_zip_of_tests(zip_path,
else report_lib.FAILED)
report["toco_log"] = toco_log
- if FLAGS.save_graphdefs:
+ if True or FLAGS.save_graphdefs:
archive.writestr(label + ".pbtxt",
text_format.MessageToString(graph_def),
zipfile.ZIP_DEFLATED)
@@ -734,21 +740,22 @@ def make_constant_tests(zip_path):
test_parameters = [{
"dtype": [tf.float32, tf.int32],
- "input_shape": [[1], [2], [1, 1, 1, 1], [2, 2, 2, 2]],
+ "input_shape": [[], [1], [2], [1, 1, 1, 1], [2, 2, 2, 2]],
}]
def build_graph(parameters):
- # Since Toco & Tflite can't have a single constant op in the entire graph,
- # this test adds a zero tensor with a constant op tensor.
- input1 = tf.placeholder(dtype=parameters["dtype"], name="input1",
- shape=parameters["input_shape"])
- out = tf.ones(parameters["input_shape"], dtype=parameters["dtype"]) + input1
- return [input1], [out]
+ dummy_input = tf.placeholder(
+ dtype=parameters["dtype"],
+ name="input1",
+ shape=parameters["input_shape"])
+ out = tf.constant(
+ create_tensor_data(parameters["dtype"], parameters["input_shape"]))
+ return [dummy_input], [out]
def build_inputs(parameters, sess, inputs, outputs):
- input1 = np.zeros(parameters["input_shape"],
- dtype=_TF_TYPE_INFO[parameters["dtype"]][0])
- return [input1], sess.run(outputs, feed_dict={inputs[0]: input1})
+ dummy_input = np.zeros(
+ parameters["input_shape"], dtype=_TF_TYPE_INFO[parameters["dtype"]][0])
+ return [dummy_input], sess.run(outputs, feed_dict={inputs[0]: dummy_input})
make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs)
@@ -1608,6 +1615,11 @@ def make_reshape_tests(zip_path):
"input_shape": [[3, 4, 5, 7], [4, 105], [21, 5, 2, 2], [420]],
"output_shape": [[15, 28], [420], [1, -1, 5, 7], [-1]],
"constant_shape": [True, False],
+ }, {
+ "dtype": [tf.float32],
+ "input_shape": [[1]],
+ "output_shape": [[]],
+ "constant_shape": [True, False],
}]
def build_graph(parameters):
@@ -1665,6 +1677,65 @@ def make_shape_tests(zip_path):
make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs)
+def make_one_hot_tests(zip_path):
+ """Make a set of tests to do one_hot."""
+
+ test_parameters = [{
+ "indices_type": [tf.int32, tf.int64],
+ "indices_shape": [[3], [4, 4], [1, 5], [5, 1]],
+ "axis": [0, 1],
+ "dtype": [tf.int32, tf.int64, tf.float32],
+ "provide_optional_inputs": [True, False],
+ }]
+
+ def build_graph(parameters):
+ indices = tf.placeholder(
+ dtype=parameters["indices_type"],
+ name="indices",
+ shape=parameters["indices_shape"])
+ depth = tf.placeholder(dtype=tf.int32, name="depth", shape=())
+
+ if not parameters["provide_optional_inputs"]:
+ out = tf.one_hot(indices=indices, depth=depth)
+ return [indices, depth], [out]
+
+ on_value = tf.placeholder(
+ dtype=parameters["dtype"], name="on_value", shape=())
+ off_value = tf.placeholder(
+ dtype=parameters["dtype"], name="off_value", shape=())
+ out = tf.one_hot(
+ indices=indices,
+ depth=depth,
+ on_value=on_value,
+ off_value=off_value,
+ axis=parameters["axis"],
+ dtype=parameters["dtype"])
+ return [indices, depth, on_value, off_value], [out]
+
+ def build_inputs(parameters, sess, inputs, outputs):
+ input_values = [
+ create_tensor_data(
+ parameters["indices_type"],
+ shape=parameters["indices_shape"],
+ min_value=-1,
+ max_value=10),
+ create_tensor_data(tf.int32, shape=None, min_value=1, max_value=10),
+ ]
+
+ if parameters["provide_optional_inputs"]:
+ input_values.append(
+ create_tensor_data(
+ parameters["dtype"], shape=None, min_value=1, max_value=10))
+ input_values.append(
+ create_tensor_data(
+ parameters["dtype"], shape=None, min_value=-1, max_value=0))
+
+ return input_values, sess.run(
+ outputs, feed_dict=dict(zip(inputs, input_values)))
+
+ make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs)
+
+
def make_resize_bilinear_tests(zip_path):
"""Make a set of tests to do resize_bilinear."""
@@ -2918,6 +2989,35 @@ def make_pack_tests(zip_path):
make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs)
+def make_logical_or_tests(zip_path):
+ """Make a set of tests to do logical_or."""
+
+ test_parameters = [{
+ "input_shape_pair": [([], []), ([1, 1, 1, 3], [1, 1, 1, 3]),
+ ([2, 3, 4, 5], [2, 3, 4, 5]), ([2, 3, 3], [2, 3]),
+ ([5, 5], [1]), ([10], [2, 4, 10])],
+ }]
+
+ def build_graph(parameters):
+ """Build the logical_or op testing graph."""
+ input_value1 = tf.placeholder(
+ dtype=tf.bool, name="input1", shape=parameters["input_shape_pair"][0])
+ input_value2 = tf.placeholder(
+ dtype=tf.bool, name="input2", shape=parameters["input_shape_pair"][1])
+ out = tf.logical_or(input_value1, input_value2)
+ return [input_value1, input_value2], [out]
+
+ def build_inputs(parameters, sess, inputs, outputs):
+ input_value1 = create_tensor_data(tf.bool,
+ parameters["input_shape_pair"][0])
+ input_value2 = create_tensor_data(tf.bool,
+ parameters["input_shape_pair"][1])
+ return [input_value1, input_value2], sess.run(
+ outputs, feed_dict=dict(zip(inputs, [input_value1, input_value2])))
+
+ make_zip_of_tests(zip_path, test_parameters, build_graph, build_inputs)
+
+
# Toco binary path provided by the generate rule.
bin_path = None
diff --git a/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc b/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc
index 770092e12c..106cbc1b8e 100644
--- a/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc
+++ b/tensorflow/contrib/lite/testing/generated_examples_zip_test.cc
@@ -226,7 +226,8 @@ TEST_P(OpsTest, RunZipTests) {
string message = test_driver.GetErrorMessage();
if (bug_number.empty()) {
if (FLAGS_use_nnapi && FLAGS_ignore_unsupported_nnapi && !result) {
- EXPECT_EQ(message, string("Failed to invoke interpreter")) << message;
+ EXPECT_EQ(message, string("Failed to invoke NNAPI interpreter"))
+ << message;
} else {
EXPECT_TRUE(result) << message;
}
diff --git a/tensorflow/contrib/lite/testing/tf_driver.cc b/tensorflow/contrib/lite/testing/tf_driver.cc
index d6a6ff8f56..ec435ca60d 100644
--- a/tensorflow/contrib/lite/testing/tf_driver.cc
+++ b/tensorflow/contrib/lite/testing/tf_driver.cc
@@ -179,7 +179,7 @@ void TfDriver::Invoke() {
auto status = session_->Run({input_tensors_.begin(), input_tensors_.end()},
output_names_, {}, &output_tensors_);
if (!status.ok()) {
- Invalidate("Failed to invoke interpreter");
+ Invalidate("Failed to run input data on graph");
}
}
diff --git a/tensorflow/contrib/lite/testing/util.h b/tensorflow/contrib/lite/testing/util.h
index 6d20aec141..8aa639157b 100644
--- a/tensorflow/contrib/lite/testing/util.h
+++ b/tensorflow/contrib/lite/testing/util.h
@@ -15,8 +15,39 @@ limitations under the License.
#ifndef TENSORFLOW_CONTRIB_LITE_TESTING_UTIL_H_
#define TENSORFLOW_CONTRIB_LITE_TESTING_UTIL_H_
+#include <cstdio>
+
+#include "tensorflow/contrib/lite/error_reporter.h"
+#include "tensorflow/contrib/lite/string.h"
+
namespace tflite {
+// An ErrorReporter that collects error message in a string, in addition
+// to printing to stderr.
+class TestErrorReporter : public ErrorReporter {
+ public:
+ int Report(const char* format, va_list args) override {
+ char buffer[1024];
+ int size = vsnprintf(buffer, sizeof(buffer), format, args);
+ fprintf(stderr, "%s", buffer);
+ error_messages_ += buffer;
+ num_calls_++;
+ return size;
+ }
+
+ void Reset() {
+ num_calls_ = 0;
+ error_messages_.clear();
+ }
+
+ int num_calls() const { return num_calls_; }
+ const string& error_messages() const { return error_messages_; }
+
+ private:
+ int num_calls_ = 0;
+ string error_messages_;
+};
+
inline void LogToStderr() {
#ifdef PLATFORM_GOOGLE
FLAGS_logtostderr = true;
diff --git a/tensorflow/contrib/lite/toco/export_tensorflow.cc b/tensorflow/contrib/lite/toco/export_tensorflow.cc
index b79bb300f0..378212cb74 100644
--- a/tensorflow/contrib/lite/toco/export_tensorflow.cc
+++ b/tensorflow/contrib/lite/toco/export_tensorflow.cc
@@ -1316,6 +1316,20 @@ void ConvertResizeBilinearOperator(const Model& model,
(*resize_op->mutable_attr())["align_corners"].set_b(src_op.align_corners);
}
+void ConvertOneHotOperator(const Model& model, const OneHotOperator& src_op,
+ GraphDef* tensorflow_graph) {
+ tensorflow::NodeDef* onehot_op = tensorflow_graph->add_node();
+ onehot_op->set_op("OneHot");
+ onehot_op->set_name(src_op.outputs[0]);
+ CHECK_EQ(src_op.inputs.size(), 4);
+ for (const auto& input : src_op.inputs) {
+ *onehot_op->add_input() = input;
+ }
+ (*onehot_op->mutable_attr())["T"].set_type(
+ GetTensorFlowDataType(model, src_op.outputs[0]));
+ (*onehot_op->mutable_attr())["axis"].set_i(src_op.axis);
+}
+
namespace {
// TODO(aselle): Remove when available in absl
absl::string_view FindLongestCommonPrefix(absl::string_view a,
@@ -1911,6 +1925,21 @@ void ConvertLogicalNotOperator(const Model& model,
*logical_op->add_input() = src_op.inputs[0];
}
+void ConvertLogicalOrOperator(const Model& model,
+ const LogicalOrOperator& src_op,
+ const char* op_name, GraphDef* tensorflow_graph) {
+ tensorflow::NodeDef* logical_or_op = tensorflow_graph->add_node();
+ logical_or_op->set_op(op_name);
+ logical_or_op->set_name(src_op.outputs[0]);
+ CHECK_EQ(src_op.inputs.size(), 2);
+ for (int i = 0; i < 2; ++i) {
+ *logical_or_op->add_input() = src_op.inputs[i];
+ }
+ const tensorflow::DataType data_type =
+ GetTensorFlowDataType(model, src_op.inputs[0]);
+ (*logical_or_op->mutable_attr())["T"].set_type(data_type);
+}
+
void ConvertOperator(const Model& model, const Operator& src_op,
GraphDef* tensorflow_graph) {
if (src_op.fused_activation_function != FusedActivationFunctionType::kNone) {
@@ -2158,6 +2187,13 @@ void ConvertOperator(const Model& model, const Operator& src_op,
ConvertLogicalNotOperator(model,
static_cast<const LogicalNotOperator&>(src_op),
tensorflow_graph);
+ } else if (src_op.type == OperatorType::kOneHot) {
+ ConvertOneHotOperator(model, static_cast<const OneHotOperator&>(src_op),
+ tensorflow_graph);
+ } else if (src_op.type == OperatorType::kLogicalOr) {
+ ConvertLogicalOrOperator(model,
+ static_cast<const LogicalOrOperator&>(src_op),
+ "LogicalOr", tensorflow_graph);
} else {
LOG(FATAL) << "Unhandled operator type " << OperatorTypeName(src_op.type);
}
diff --git a/tensorflow/contrib/lite/toco/graph_transformations/ensure_uint8_weights_safe_for_fast_int8_kernels.cc b/tensorflow/contrib/lite/toco/graph_transformations/ensure_uint8_weights_safe_for_fast_int8_kernels.cc
index 75642bbc37..c13fc0de75 100644
--- a/tensorflow/contrib/lite/toco/graph_transformations/ensure_uint8_weights_safe_for_fast_int8_kernels.cc
+++ b/tensorflow/contrib/lite/toco/graph_transformations/ensure_uint8_weights_safe_for_fast_int8_kernels.cc
@@ -181,7 +181,7 @@ bool EnsureUint8WeightsSafeForFastInt8Kernels::Run(Model* model,
// future without worrying.
static constexpr int kMinDistanceBetweenBadValues = 16;
if (distance < kMinDistanceBetweenBadValues) {
- if (allow_nudging_weights()) {
+ if (allow_nudging_weights() || has_default_ranges_flag()) {
buffer_data[i] = 1;
changed = true;
continue;
@@ -200,6 +200,15 @@ bool EnsureUint8WeightsSafeForFastInt8Kernels::Run(Model* model,
}
if (changed) {
+ if (has_default_ranges_flag()) {
+ std::cerr
+ << "Since the specified values of --default_ranges_min and "
+ "--default_ranges_max result in values incompatible with TFLite's "
+ "fast int8 kernels, "
+ "--allow_nudging_weights_to_use_fast_gemm_kernel "
+ "has been enabled. This may affect the accuracy of the model."
+ << std::endl;
+ }
AddMessageF("Tweaked weights values for %s", LogName(op));
}
diff --git a/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h b/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h
index b7634e28c6..8d9a4c4700 100644
--- a/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h
+++ b/tensorflow/contrib/lite/toco/graph_transformations/graph_transformations.h
@@ -262,8 +262,12 @@ class EnsureUint8WeightsSafeForFastInt8Kernels : public GraphTransformation {
bool allow_nudging_weights() const { return allow_nudging_weights_; }
void set_allow_nudging_weights(bool val) { allow_nudging_weights_ = val; }
+ bool has_default_ranges_flag() const { return has_default_ranges_flag_; }
+ void set_has_default_ranges_flag(bool val) { has_default_ranges_flag_ = val; }
+
private:
bool allow_nudging_weights_ = false;
+ bool has_default_ranges_flag_ = false;
};
#undef DECLARE_GRAPH_TRANSFORMATION
diff --git a/tensorflow/contrib/lite/toco/graph_transformations/propagate_array_data_types.cc b/tensorflow/contrib/lite/toco/graph_transformations/propagate_array_data_types.cc
index 9848d55c83..f033ee013e 100644
--- a/tensorflow/contrib/lite/toco/graph_transformations/propagate_array_data_types.cc
+++ b/tensorflow/contrib/lite/toco/graph_transformations/propagate_array_data_types.cc
@@ -65,6 +65,7 @@ bool PropagateArrayDataTypes::Run(Model* model, std::size_t op_index) {
case OperatorType::kAny:
case OperatorType::kLogicalAnd:
case OperatorType::kLogicalNot:
+ case OperatorType::kLogicalOr:
// These operators unconditionally produce bool outputs
SetDataTypeForAllOutputs(model, op, ArrayDataType::kBool);
break;
@@ -141,7 +142,8 @@ bool PropagateArrayDataTypes::Run(Model* model, std::size_t op_index) {
CHECK_EQ(op->inputs.size(), 2);
CHECK_EQ(op->outputs.size(), 2);
CHECK(model->GetArray(op->inputs[1]).data_type == ArrayDataType::kInt32);
- model->GetArray(op->outputs[0]).data_type = model->GetArray(op->inputs[0]).data_type;
+ model->GetArray(op->outputs[0]).data_type =
+ model->GetArray(op->inputs[0]).data_type;
model->GetArray(op->outputs[1]).data_type = ArrayDataType ::kInt32;
break;
}
@@ -154,8 +156,8 @@ bool PropagateArrayDataTypes::Run(Model* model, std::size_t op_index) {
return false;
}
for (int i = 0; i < op->outputs.size(); ++i) {
- auto output = op->outputs[i];
- auto data_type = unsupported_op->output_data_types[i];
+ const string& output = op->outputs[i];
+ const ArrayDataType data_type = unsupported_op->output_data_types[i];
model->GetArray(output).data_type = data_type;
}
break;
@@ -201,6 +203,18 @@ bool PropagateArrayDataTypes::Run(Model* model, std::size_t op_index) {
SetDataTypeForAllOutputs(model, op, data_type);
break;
}
+ case OperatorType::kOneHot: {
+ CHECK_EQ(op->inputs.size(), 4);
+ CHECK_EQ(op->outputs.size(), 1);
+ const ArrayDataType on_value_type =
+ model->GetArray(op->inputs[OneHotOperator::ON_VALUE_INPUT]).data_type;
+ const ArrayDataType off_value_type =
+ model->GetArray(op->inputs[OneHotOperator::OFF_VALUE_INPUT])
+ .data_type;
+ CHECK(on_value_type == off_value_type);
+ model->GetArray(op->outputs[0]).data_type = on_value_type;
+ break;
+ }
default: {
// These operators produce outputs with the same type as their 1st input
CHECK_GT(op->inputs.size(), 0);
diff --git a/tensorflow/contrib/lite/toco/graph_transformations/propagate_fixed_sizes.cc b/tensorflow/contrib/lite/toco/graph_transformations/propagate_fixed_sizes.cc
index 62ed5c46e9..3c9379fd87 100644
--- a/tensorflow/contrib/lite/toco/graph_transformations/propagate_fixed_sizes.cc
+++ b/tensorflow/contrib/lite/toco/graph_transformations/propagate_fixed_sizes.cc
@@ -1578,6 +1578,61 @@ void ProcessAnyOperator(Model* model, AnyOperator* op) {
}
}
+void ProcessOneHotOperator(Model* model, OneHotOperator* op) {
+ CHECK_EQ(op->inputs.size(), 4);
+ CHECK_EQ(op->outputs.size(), 1);
+ auto& output_array = model->GetArray(op->outputs[0]);
+ if (output_array.has_shape()) {
+ // Shape already propagated
+ return;
+ }
+
+ // Yield until indices dims have been resolved.
+ const auto& indices_array =
+ model->GetArray(op->inputs[OneHotOperator::INDICES_INPUT]);
+ if (!indices_array.has_shape()) {
+ return;
+ }
+
+ // Yield until depth is constant and dims have been resolved.
+ if (!IsConstantParameterArray(*model,
+ op->inputs[OneHotOperator::DEPTH_INPUT])) {
+ return;
+ }
+ const auto& depth_array =
+ model->GetArray(op->inputs[OneHotOperator::DEPTH_INPUT]);
+ if (!depth_array.has_shape()) {
+ return;
+ }
+
+ CHECK(depth_array.data_type == ArrayDataType::kInt32)
+ << "Depth array must be int32.";
+ CHECK_EQ(RequiredBufferSizeForShape(depth_array.shape()), 1)
+ << "Depth array must be scalar.";
+
+ const int depth = depth_array.GetBuffer<ArrayDataType::kInt32>().data[0];
+ CHECK_GE(depth, 0) << "Depth must be non-negative.";
+
+ const int indices_dims = indices_array.shape().dimensions_count();
+ const int output_dims = indices_dims + 1;
+ const int axis = op->axis == -1 ? indices_dims : op->axis;
+ CHECK_GE(axis, 0) << "Resolved axis must be non-negative.";
+
+ auto* mutable_dims = output_array.mutable_shape()->mutable_dims();
+ mutable_dims->resize(output_dims);
+ for (int i = 0; i < output_dims; ++i) {
+ int dim = 0;
+ if (i < axis) {
+ dim = indices_array.shape().dims(i);
+ } else if (i == axis) {
+ dim = depth;
+ } else {
+ dim = indices_array.shape().dims(i - 1);
+ }
+ (*mutable_dims)[i] = dim;
+ }
+}
+
} // namespace
bool PropagateFixedSizes::Run(Model* model, std::size_t op_index) {
@@ -1618,6 +1673,7 @@ bool PropagateFixedSizes::Run(Model* model, std::size_t op_index) {
case OperatorType::kSin:
case OperatorType::kLogicalAnd:
case OperatorType::kLogicalNot:
+ case OperatorType::kLogicalOr:
ProcessSimpleOperator(model, op, 0);
break;
case OperatorType::kGather:
@@ -1786,8 +1842,19 @@ bool PropagateFixedSizes::Run(Model* model, std::size_t op_index) {
ProcessArgMinMaxOperator<ArgMinOperator>(
model, static_cast<ArgMinOperator*>(op));
break;
- case OperatorType::kUnsupported:
+ case OperatorType::kUnsupported: {
+ const auto* unsupported_op =
+ static_cast<TensorFlowUnsupportedOperator*>(op);
+ // Attribute can be not specified, ignore it.
+ if (unsupported_op->output_shapes.size() < op->outputs.size()) {
+ return false;
+ }
+ for (int i = 0; i < op->outputs.size(); ++i) {
+ const string& output = op->outputs[i];
+ model->GetArray(output).copy_shape(unsupported_op->output_shapes.at(i));
+ }
break;
+ }
case OperatorType::kSvdf:
ProcessSvdfOperator(model, static_cast<SvdfOperator*>(op));
break;
@@ -1814,6 +1881,9 @@ bool PropagateFixedSizes::Run(Model* model, std::size_t op_index) {
case OperatorType::kAny:
ProcessAnyOperator(model, static_cast<AnyOperator*>(op));
break;
+ case OperatorType::kOneHot:
+ ProcessOneHotOperator(model, static_cast<OneHotOperator*>(op));
+ break;
default:
// Unimplemented, another graph transformation should drop it.
LOG(FATAL) << "Unhandled operator type " << OperatorTypeName(op->type);
diff --git a/tensorflow/contrib/lite/toco/import_tensorflow.cc b/tensorflow/contrib/lite/toco/import_tensorflow.cc
index 032c863945..9a3db5c888 100644
--- a/tensorflow/contrib/lite/toco/import_tensorflow.cc
+++ b/tensorflow/contrib/lite/toco/import_tensorflow.cc
@@ -215,7 +215,7 @@ tensorflow::Status ImportFloatArray(const TensorProto& input_tensor,
Array* output_array) {
CHECK_EQ(input_tensor.dtype(), DT_FLOAT);
const auto& input_shape = input_tensor.tensor_shape();
- CHECK_LE(input_shape.dim_size(), 4);
+ CHECK_LE(input_shape.dim_size(), 6);
int input_flat_size;
auto status = ImportShape(input_shape.dim(), &input_flat_size,
output_array->mutable_shape());
@@ -253,7 +253,7 @@ tensorflow::Status ImportQuint8Array(const TensorProto& input_tensor,
Array* output_array) {
CHECK_EQ(input_tensor.dtype(), DT_QUINT8);
const auto& input_shape = input_tensor.tensor_shape();
- CHECK_LE(input_shape.dim_size(), 4);
+ CHECK_LE(input_shape.dim_size(), 6);
int input_flat_size;
auto status = ImportShape(input_shape.dim(), &input_flat_size,
output_array->mutable_shape());
@@ -290,7 +290,7 @@ tensorflow::Status ImportInt32Array(const TensorProto& input_tensor,
Array* output_array) {
CHECK_EQ(input_tensor.dtype(), DT_INT32);
const auto& input_shape = input_tensor.tensor_shape();
- CHECK_LE(input_shape.dim_size(), 4);
+ CHECK_LE(input_shape.dim_size(), 6);
int input_flat_size;
auto status = ImportShape(input_shape.dim(), &input_flat_size,
output_array->mutable_shape());
@@ -326,7 +326,7 @@ tensorflow::Status ImportInt64Array(const TensorProto& input_tensor,
Array* output_array) {
CHECK_EQ(input_tensor.dtype(), DT_INT64);
const auto& input_shape = input_tensor.tensor_shape();
- CHECK_LE(input_shape.dim_size(), 4);
+ CHECK_LE(input_shape.dim_size(), 6);
int input_flat_size;
auto status = ImportShape(input_shape.dim(), &input_flat_size,
output_array->mutable_shape());
@@ -363,7 +363,7 @@ tensorflow::Status ImportBoolArray(const TensorProto& input_tensor,
Array* output_array) {
CHECK_EQ(input_tensor.dtype(), DT_BOOL);
const auto& input_shape = input_tensor.tensor_shape();
- CHECK_LE(input_shape.dim_size(), 4);
+ CHECK_LE(input_shape.dim_size(), 6);
int input_flat_size;
auto status = ImportShape(input_shape.dim(), &input_flat_size,
output_array->mutable_shape());
@@ -409,7 +409,7 @@ tensorflow::Status ImportStringArray(const TensorProto& input_tensor,
Array* output_array) {
CHECK_EQ(input_tensor.dtype(), DT_STRING);
const auto& input_shape = input_tensor.tensor_shape();
- CHECK_LE(input_shape.dim_size(), 4);
+ CHECK_LE(input_shape.dim_size(), 6);
int input_flat_size;
auto status = ImportShape(input_shape.dim(), &input_flat_size,
output_array->mutable_shape());
@@ -1045,6 +1045,11 @@ tensorflow::Status ConvertSimpleOperator(
tensorflow::Status ConvertUnsupportedOperator(
const NodeDef& node, const TensorFlowImportFlags& tf_import_flags,
Model* model) {
+ // Names of special attributes in TF graph that are used by Toco.
+ static constexpr char kAttrOutputQuantized[] = "_output_quantized";
+ static constexpr char kAttrOutputTypes[] = "_output_types";
+ static constexpr char kAttrOutputShapes[] = "_output_shapes";
+
LOG(INFO) << "Converting unsupported operation: " << node.op();
auto* op = new TensorFlowUnsupportedOperator;
const int num_inputs = GetInputsCount(node, tf_import_flags);
@@ -1055,11 +1060,11 @@ tensorflow::Status ConvertUnsupportedOperator(
op->tensorflow_op = node.op();
node.SerializeToString(&op->tensorflow_node_def);
model->operators.emplace_back(op);
- if (HasAttr(node, "_output_quantized")) {
- op->quantized = GetBoolAttr(node, "_output_quantized");
+ if (HasAttr(node, kAttrOutputQuantized)) {
+ op->quantized = GetBoolAttr(node, kAttrOutputQuantized);
}
- if (HasAttr(node, "_output_types")) {
- const auto& output_types = GetListAttr(node, "_output_types");
+ if (HasAttr(node, kAttrOutputTypes)) {
+ const auto& output_types = GetListAttr(node, kAttrOutputTypes);
for (int i = 0; i < output_types.type_size(); ++i) {
op->output_data_types.push_back(ConvertDataType(output_types.type(i)));
}
@@ -1067,6 +1072,19 @@ tensorflow::Status ConvertUnsupportedOperator(
const auto& output_type = GetDataTypeAttr(node, "Tout");
op->output_data_types.push_back(ConvertDataType(output_type));
}
+ if (HasAttr(node, kAttrOutputShapes)) {
+ const auto& output_shapes = GetListAttr(node, kAttrOutputShapes);
+ Shape output_shape;
+ for (int i = 0; i < output_shapes.shape_size(); ++i) {
+ const auto status =
+ ImportShape(output_shapes.shape(i).dim(), /*input_flat_size=*/nullptr,
+ &output_shape);
+ if (!status.ok()) {
+ return status;
+ }
+ op->output_shapes.push_back(output_shape);
+ }
+ }
return tensorflow::Status::OK();
}
@@ -1815,6 +1833,27 @@ tensorflow::Status ConvertSparseToDenseOperator(
return tensorflow::Status::OK();
}
+tensorflow::Status ConvertOneHotOperator(
+ const NodeDef& node, const TensorFlowImportFlags& tf_import_flags,
+ Model* model) {
+ CHECK_EQ(node.op(), "OneHot");
+ TF_QCHECK_OK(CheckInputsCount(node, tf_import_flags, 4));
+
+ const auto dtype = GetDataTypeAttr(node, "T");
+ // TODO(b/111744875): Support DT_UINT8 and quantization.
+ CHECK(dtype == DT_INT32 || dtype == DT_INT64 || dtype == DT_FLOAT ||
+ dtype == DT_BOOL);
+
+ auto op = absl::make_unique<OneHotOperator>();
+ op->axis = HasAttr(node, "axis") ? GetIntAttr(node, "axis") : -1;
+ for (const string& input : node.input()) {
+ op->inputs.push_back(input);
+ }
+ op->outputs.push_back(node.name());
+ model->operators.emplace_back(op.release());
+ return tensorflow::Status::OK();
+}
+
} // namespace
namespace internal {
@@ -1875,9 +1914,10 @@ ConverterMapType GetTensorFlowNodeConverterMap() {
{"Less", ConvertSimpleOperator<TensorFlowLessOperator, 2>},
{"LessEqual", ConvertSimpleOperator<TensorFlowLessEqualOperator, 2>},
{"Log", ConvertSimpleOperator<LogOperator, 1>},
- {"LogSoftmax", ConvertSimpleOperator<LogSoftmaxOperator, 1>},
{"LogicalAnd", ConvertSimpleOperator<LogicalAndOperator, 2>},
+ {"LogicalOr", ConvertSimpleOperator<LogicalOrOperator, 2>},
{"LogicalNot", ConvertSimpleOperator<LogicalNotOperator, 1>},
+ {"LogSoftmax", ConvertSimpleOperator<LogSoftmaxOperator, 1>},
{"MatMul", ConvertMatMulOperator},
{"Max", ConvertReduceOperator<TensorFlowMaxOperator>},
{"MaxPool", ConvertMaxPoolOperator},
@@ -1891,6 +1931,7 @@ ConverterMapType GetTensorFlowNodeConverterMap() {
{"NextIteration", ConvertOperatorSpecialCasedAsRNNBackEdge},
{"NoOp", ConvertNoOpOperator},
{"NotEqual", ConvertSimpleOperator<TensorFlowNotEqualOperator, 2>},
+ {"OneHot", ConvertOneHotOperator},
{"Pack", ConvertPackOperator},
{"Pad", ConvertSimpleOperator<PadOperator, 2>},
{"PadV2", ConvertSimpleOperator<PadV2Operator, 3>},
diff --git a/tensorflow/contrib/lite/toco/model.h b/tensorflow/contrib/lite/toco/model.h
index d629787939..7d0dbfcc05 100644
--- a/tensorflow/contrib/lite/toco/model.h
+++ b/tensorflow/contrib/lite/toco/model.h
@@ -64,6 +64,7 @@ enum class OperatorType : uint8 {
kMaxPool,
kFakeQuant,
kMul,
+ kOneHot,
kRandomUniform,
kRange,
kRank,
@@ -146,6 +147,7 @@ enum class OperatorType : uint8 {
kAny,
kLogicalAnd,
kLogicalNot,
+ kLogicalOr,
};
// Helper to deal with TensorFlow arrays using a different ordering of
@@ -292,6 +294,46 @@ struct Buffer : GenericBuffer {
std::vector<DataType<A>> data;
};
+class Shape {
+ public:
+ // For Shape, we stick to half-way encapsulation for now:
+ // we hide the raw dims_ member, but expose it raw by accessors
+ // because from some brainstorming, it's not at all easy to
+ // anticipate which flavor of more hermetic encapsulation would
+ // actually buy us future-proof-ness without being needlessly
+ // cumbersome.
+ Shape() {}
+ Shape(std::initializer_list<int> dim_list) : dims_(dim_list) {}
+
+ void ReplaceDims(std::initializer_list<int> dim_list) {
+ dims_ = std::vector<int>(dim_list);
+ }
+
+ const std::vector<int>& dims() const { return dims_; }
+ std::vector<int>* mutable_dims() { return &dims_; }
+ const int dimensions_count() const { return dims_.size(); }
+
+ // We still have that one convenience accessor to avoid
+ // the awkward double bracket issue: shape.dims()[i].
+ int dims(int i) const {
+ // Always check for out-of-bounds accesses, even in optimized builds where
+ // standard assertions are disabled. Out-of-bounds access here is a common
+ // occurrence.
+ CHECK_GE(i, 0);
+ CHECK_GT(dims_.size(), i);
+ return dims_[i];
+ }
+
+ bool operator==(const Shape& comp) const {
+ return (this->dims_ == comp.dims());
+ }
+
+ bool operator!=(const Shape& comp) const { return !((*this) == comp); }
+
+ private:
+ std::vector<int> dims_;
+};
+
// Base class for all operator classes.
struct Operator {
// Non-default-constructible: only OperatorType-specific subclass
@@ -1469,6 +1511,8 @@ struct TensorFlowUnsupportedOperator : Operator {
bool quantized = false;
// Output data types
std::vector<ArrayDataType> output_data_types;
+ // Output shapes.
+ std::vector<Shape> output_shapes;
};
// Softmax activation function.
@@ -1726,6 +1770,38 @@ struct LogicalNotOperator : Operator {
LogicalNotOperator() : Operator(OperatorType::kLogicalNot) {}
};
+// OneHot operator:
+//
+// Inputs:
+// Inputs[0]: required: indices.
+// Inputs[1]: required: depth.
+// Inputs[2]: required: on_value.
+// Inputs[3]: required: off_value.
+//
+// TensorFlow equivalent: OneHot.
+struct OneHotOperator : Operator {
+ enum Inputs {
+ INDICES_INPUT = 0,
+ DEPTH_INPUT = 1,
+ ON_VALUE_INPUT = 2,
+ OFF_VALUE_INPUT = 3,
+ };
+
+ OneHotOperator() : Operator(OperatorType::kOneHot) {}
+ int axis = -1;
+};
+
+// LogicalOr operator:
+//
+// Inputs:
+// Inputs[0]: required: A Bool tensor.
+// Inputs[1]: required: A Bool tensor.
+//
+// TensorFlow equivalent: LogicalOr.
+struct LogicalOrOperator : Operator {
+ LogicalOrOperator() : Operator(OperatorType::kLogicalOr) {}
+};
+
// Alloc's are used for transient arrays only. An Alloc specifies which interval
// of the "transient_data" workspace buffer passed to inference functions, is to
// be used for the transient array at hand. The 'start' and 'end' values are
@@ -1739,46 +1815,6 @@ inline bool operator<(const Alloc& a, const Alloc& b) {
return a.start < b.start;
}
-class Shape {
- public:
- // For Shape, we stick to half-way encapsulation for now:
- // we hide the raw dims_ member, but expose it raw by accessors
- // because from some brainstorming, it's not at all easy to
- // anticipate which flavor of more hermetic encapsulation would
- // actually buy us future-proof-ness without being needlessly
- // cumbersome.
- Shape() {}
- Shape(std::initializer_list<int> dim_list) : dims_(dim_list) {}
-
- void ReplaceDims(std::initializer_list<int> dim_list) {
- dims_ = std::vector<int>(dim_list);
- }
-
- const std::vector<int>& dims() const { return dims_; }
- std::vector<int>* mutable_dims() { return &dims_; }
- const int dimensions_count() const { return dims_.size(); }
-
- // We still have that one convenience accessor to avoid
- // the awkward double bracket issue: shape.dims()[i].
- int dims(int i) const {
- // Always check for out-of-bounds accesses, even in optimized builds where
- // standard assertions are disabled. Out-of-bounds access here is a common
- // occurrence.
- CHECK_GE(i, 0);
- CHECK_GT(dims_.size(), i);
- return dims_[i];
- }
-
- bool operator==(const Shape& comp) const {
- return (this->dims_ == comp.dims());
- }
-
- bool operator!=(const Shape& comp) const { return !((*this) == comp); }
-
- private:
- std::vector<int> dims_;
-};
-
// Array represents an array (either a constant parameter array or an
// activations array) in a Model.
struct Array {
diff --git a/tensorflow/contrib/lite/toco/tflite/operator.cc b/tensorflow/contrib/lite/toco/tflite/operator.cc
index 4b2ef756cc..9380168f30 100644
--- a/tensorflow/contrib/lite/toco/tflite/operator.cc
+++ b/tensorflow/contrib/lite/toco/tflite/operator.cc
@@ -1053,6 +1053,23 @@ class Shape
int GetVersion(const Operator& op) const override { return 1; }
};
+class OneHot : public BuiltinOperator<OneHotOperator, ::tflite::OneHotOptions,
+ ::tflite::BuiltinOptions_OneHotOptions> {
+ public:
+ using BuiltinOperator::BuiltinOperator;
+ flatbuffers::Offset<TfLiteOptions> WriteOptions(
+ const TocoOperator& op,
+ flatbuffers::FlatBufferBuilder* builder) const override {
+ return ::tflite::CreateOneHotOptions(*builder, op.axis);
+ }
+ void ReadOptions(const TfLiteOptions& options,
+ TocoOperator* op) const override {
+ op->axis = options.axis();
+ }
+
+ int GetVersion(const Operator& op) const override { return 1; }
+};
+
class TensorFlowUnsupported : public BaseOperator {
public:
using BaseOperator::BaseOperator;
@@ -1278,6 +1295,8 @@ std::vector<std::unique_ptr<BaseOperator>> BuildOperatorList() {
OperatorType::kFakeQuant));
ops.emplace_back(
new Pack(::tflite::BuiltinOperator_PACK, OperatorType::kPack));
+ ops.emplace_back(
+ new OneHot(::tflite::BuiltinOperator_ONE_HOT, OperatorType::kOneHot));
// Custom Operators.
ops.emplace_back(
@@ -1331,6 +1350,8 @@ std::vector<std::unique_ptr<BaseOperator>> BuildOperatorList() {
ops.emplace_back(
new SimpleOperator<SliceOperator>("SLICE", OperatorType::kSlice));
ops.emplace_back(new SimpleOperator<PowOperator>("POW", OperatorType::kPow));
+ ops.emplace_back(new SimpleOperator<LogicalOrOperator>(
+ "LOGICAL_OR", OperatorType::kLogicalOr));
// Element-wise operator
ops.emplace_back(new SimpleOperator<SinOperator>("SIN", OperatorType::kSin));
ops.emplace_back(new SimpleOperator<LogOperator>("LOG", OperatorType::kLog));
diff --git a/tensorflow/contrib/lite/toco/tflite/operator_test.cc b/tensorflow/contrib/lite/toco/tflite/operator_test.cc
index 44de6fbf64..384f7c118d 100644
--- a/tensorflow/contrib/lite/toco/tflite/operator_test.cc
+++ b/tensorflow/contrib/lite/toco/tflite/operator_test.cc
@@ -127,6 +127,8 @@ TEST_F(OperatorTest, SimpleOperators) {
CheckSimpleOperator<TensorFlowSqrtOperator>("SQRT", OperatorType::kSqrt);
CheckSimpleOperator<TensorFlowRsqrtOperator>("RSQRT", OperatorType::kRsqrt);
CheckSimpleOperator<PowOperator>("POW", OperatorType::kPow);
+ CheckSimpleOperator<LogicalOrOperator>("LOGICAL_OR",
+ OperatorType::kLogicalOr);
}
TEST_F(OperatorTest, BuiltinAdd) {
@@ -462,6 +464,14 @@ TEST_F(OperatorTest, BuiltinPack) {
EXPECT_EQ(op.axis, output_toco_op->axis);
}
+TEST_F(OperatorTest, BuiltinOneHot) {
+ OneHotOperator op;
+ op.axis = 2;
+ auto output_toco_op = SerializeAndDeserialize(
+ GetOperator("ONE_HOT", OperatorType::kOneHot), op);
+ EXPECT_EQ(op.axis, output_toco_op->axis);
+}
+
TEST_F(OperatorTest, TensorFlowUnsupported) {
TensorFlowUnsupportedOperator op;
op.tensorflow_op = "MyCustomUnsupportedOp";
diff --git a/tensorflow/contrib/lite/toco/toco_tooling.cc b/tensorflow/contrib/lite/toco/toco_tooling.cc
index aa7f6996eb..fcd3cbab07 100644
--- a/tensorflow/contrib/lite/toco/toco_tooling.cc
+++ b/tensorflow/contrib/lite/toco/toco_tooling.cc
@@ -309,8 +309,9 @@ void Transform(const TocoFlags& toco_flags, Model* model) {
// HardcodeMinMax to move changes through the graph as we make changes.
auto propagate_default_min_max =
absl::make_unique<PropagateDefaultMinMax>();
- if (toco_flags.has_default_ranges_min() &&
- toco_flags.has_default_ranges_max()) {
+ bool has_default_ranges_flag = (toco_flags.has_default_ranges_min() &&
+ toco_flags.has_default_ranges_max());
+ if (has_default_ranges_flag) {
propagate_default_min_max->DefineTypeRange(
ArrayDataType::kUint8, toco_flags.default_ranges_min(),
toco_flags.default_ranges_max());
@@ -335,6 +336,8 @@ void Transform(const TocoFlags& toco_flags, Model* model) {
new EnsureUint8WeightsSafeForFastInt8Kernels;
ensure_safe_for_int8_kernels->set_allow_nudging_weights(
toco_flags.allow_nudging_weights_to_use_fast_gemm_kernel());
+ ensure_safe_for_int8_kernels->set_has_default_ranges_flag(
+ has_default_ranges_flag);
RunGraphTransformations(model, "quantization graph transformations",
{
new RemoveTrivialQuantizedActivationFunc,
diff --git a/tensorflow/contrib/lite/toco/tooling_util.cc b/tensorflow/contrib/lite/toco/tooling_util.cc
index 98e416b76e..68155c7329 100644
--- a/tensorflow/contrib/lite/toco/tooling_util.cc
+++ b/tensorflow/contrib/lite/toco/tooling_util.cc
@@ -356,6 +356,7 @@ const char* OperatorTypeName(OperatorType type) {
HANDLE_OPERATORTYPENAME_CASE(ReduceMin) // Reduction Min
HANDLE_OPERATORTYPENAME_CASE(Minimum) // Element-wise Minimum
HANDLE_OPERATORTYPENAME_CASE(Neg)
+ HANDLE_OPERATORTYPENAME_CASE(OneHot)
HANDLE_OPERATORTYPENAME_CASE(Pack)
HANDLE_OPERATORTYPENAME_CASE(Pad)
HANDLE_OPERATORTYPENAME_CASE(PadV2)
@@ -402,6 +403,7 @@ const char* OperatorTypeName(OperatorType type) {
HANDLE_OPERATORTYPENAME_CASE(Any)
HANDLE_OPERATORTYPENAME_CASE(LogicalAnd)
HANDLE_OPERATORTYPENAME_CASE(LogicalNot)
+ HANDLE_OPERATORTYPENAME_CASE(LogicalOr)
default:
LOG(FATAL) << "Unhandled op type";
#undef HANDLE_OPERATORTYPENAME_CASE
@@ -1617,11 +1619,12 @@ void CheckIsReadyForQuantization(const Model& model) {
<< "Array " << input << ", which is an input to the "
<< HelpfulOperatorTypeName(*op) << " operator producing the output "
<< "array " << op->outputs[0] << ", is lacking min/max data, "
- << "which is necessary for quantization. Either target a "
- << "non-quantized output format, or change the input graph to "
- << "contain min/max information, or pass --default_ranges_min= and "
- << "--default_ranges_max= if you do not care about the accuracy of "
- << "results.";
+ << "which is necessary for quantization. If accuracy matters, either "
+ << "target a non-quantized output format, or run quantized training "
+ << "with your model from a floating point checkpoint to change the "
+ << "input graph to contain min/max information. If you don't care "
+ << "about accuracy, you can pass --default_ranges_min= and "
+ << "--default_ranges_max= for easy experimentation.";
}
}
}
diff --git a/tensorflow/contrib/model_pruning/README.md b/tensorflow/contrib/model_pruning/README.md
index 9143d082bf..dbe4e124fd 100644
--- a/tensorflow/contrib/model_pruning/README.md
+++ b/tensorflow/contrib/model_pruning/README.md
@@ -42,7 +42,7 @@ The pruning library allows for specification of the following hyper parameters:
| name | string | model_pruning | Name of the pruning specification. Used for adding summaries and ops under a common tensorflow name_scope |
| begin_pruning_step | integer | 0 | The global step at which to begin pruning |
| end_pruning_step | integer | -1 | The global step at which to terminate pruning. Defaults to -1 implying that pruning continues till the training stops |
-| do_not_prune | list of strings | [""] | list of layers names that are not pruned |
+| weight_sparsity_map | list of strings | [""] | list of weight variable name (or layer name):target sparsity pairs. Eg. [conv1:0.9,conv2/kernel:0.8]. For layers/weights not in this list, sparsity as specified by the target_sparsity hyperparameter is used. |
| threshold_decay | float | 0.9 | The decay factor to use for exponential decay of the thresholds |
| pruning_frequency | integer | 10 | How often should the masks be updated? (in # of global_steps) |
| nbins | integer | 256 | Number of bins to use for histogram computation |
diff --git a/tensorflow/contrib/model_pruning/python/pruning.py b/tensorflow/contrib/model_pruning/python/pruning.py
index da9d398cbc..723dab9369 100644
--- a/tensorflow/contrib/model_pruning/python/pruning.py
+++ b/tensorflow/contrib/model_pruning/python/pruning.py
@@ -152,8 +152,11 @@ def get_pruning_hparams():
end_pruning_step: integer
the global step at which to terminate pruning. Defaults to -1 implying
that pruning continues till the training stops
- do_not_prune: list of strings
- list of layers that are not pruned
+ weight_sparsity_map: list of strings
+ comma separed list of weight variable name:target sparsity pairs.
+ For layers/weights not in this list, sparsity as specified by the
+ target_sparsity hyperparameter is used.
+ Eg. [conv1:0.9,conv2/kernel:0.8]
threshold_decay: float
the decay factor to use for exponential decay of the thresholds
pruning_frequency: integer
@@ -200,7 +203,7 @@ def get_pruning_hparams():
name='model_pruning',
begin_pruning_step=0,
end_pruning_step=-1,
- do_not_prune=[''],
+ weight_sparsity_map=[''],
threshold_decay=0.9,
pruning_frequency=10,
nbins=256,
@@ -256,6 +259,9 @@ class Pruning(object):
# Block pooling function
self._block_pooling_function = self._spec.block_pooling_function
+ # Mapping of weight names and target sparsity
+ self._weight_sparsity_map = self._get_weight_sparsity_map()
+
def _setup_global_step(self, global_step):
graph_global_step = global_step
if graph_global_step is None:
@@ -306,15 +312,36 @@ class Pruning(object):
'last_mask_update_step', dtype=dtypes.int32)
return last_update_step
- def _exists_in_do_not_prune_list(self, tensor_name):
- do_not_prune_list = self._spec.do_not_prune
- if not do_not_prune_list[0]:
- return False
- for layer_name in do_not_prune_list:
- if tensor_name.find(layer_name) != -1:
- return True
-
- return False
+ def _get_weight_sparsity_map(self):
+ """Return the map of weight_name:sparsity parsed from the hparams."""
+ weight_sparsity_map = {}
+ val_list = self._spec.weight_sparsity_map
+ filtered_val_list = [l for l in val_list if l]
+ for val in filtered_val_list:
+ weight_name, sparsity = val.split(':')
+ if float(sparsity) >= 1.0:
+ raise ValueError('Weight sparsity can not exceed 1.0')
+ weight_sparsity_map[weight_name] = float(sparsity)
+
+ return weight_sparsity_map
+
+ def _get_sparsity(self, weight_name):
+ """Return target sparsity for the given layer/weight name."""
+ target_sparsity = [
+ sparsity for name, sparsity in self._weight_sparsity_map.items()
+ if weight_name.find(name) != -1
+ ]
+ if not target_sparsity:
+ return self._sparsity
+
+ if len(target_sparsity) > 1:
+ raise ValueError(
+ 'Multiple matches in weight_sparsity_map for weight %s' % weight_name)
+ # TODO(suyoggupta): This will work when initial_sparsity = 0. Generalize
+ # to handle other cases as well.
+ return math_ops.mul(
+ self._sparsity,
+ math_ops.div(target_sparsity[0], self._spec.target_sparsity))
def _update_mask(self, weights, threshold):
"""Updates the mask for a given weight tensor.
@@ -342,6 +369,8 @@ class Pruning(object):
if self._sparsity is None:
raise ValueError('Sparsity variable undefined')
+ sparsity = self._get_sparsity(weights.op.name)
+
with ops.name_scope(weights.op.name + '_pruning_ops'):
abs_weights = math_ops.abs(weights)
max_value = math_ops.reduce_max(abs_weights)
@@ -354,7 +383,7 @@ class Pruning(object):
math_ops.div(
math_ops.reduce_sum(
math_ops.cast(
- math_ops.less(norm_cdf, self._sparsity), dtypes.float32)),
+ math_ops.less(norm_cdf, sparsity), dtypes.float32)),
float(self._spec.nbins)), max_value)
smoothed_threshold = math_ops.add_n([
@@ -453,10 +482,6 @@ class Pruning(object):
if is_partitioned:
weight = weight.as_tensor()
- if self._spec.do_not_prune:
- if self._exists_in_do_not_prune_list(mask.name):
- continue
-
new_threshold, new_mask = self._maybe_update_block_mask(weight, threshold)
self._assign_ops.append(
pruning_utils.variable_assign(threshold, new_threshold))
@@ -507,22 +532,15 @@ class Pruning(object):
no_update_op)
def add_pruning_summaries(self):
- """Adds summaries for this pruning spec.
-
- Args: none
-
- Returns: none
- """
+ """Adds summaries of weight sparsities and thresholds."""
with ops.name_scope(self._spec.name + '_summaries'):
summary.scalar('sparsity', self._sparsity)
summary.scalar('last_mask_update_step', self._last_update_step)
masks = get_masks()
thresholds = get_thresholds()
for mask, threshold in zip(masks, thresholds):
- if not self._exists_in_do_not_prune_list(mask.name):
- summary.scalar(mask.op.name + '/sparsity',
- nn_impl.zero_fraction(mask))
- summary.scalar(threshold.op.name + '/threshold', threshold)
+ summary.scalar(mask.op.name + '/sparsity', nn_impl.zero_fraction(mask))
+ summary.scalar(threshold.op.name + '/threshold', threshold)
def print_hparams(self):
logging.info(self._spec.to_json())
diff --git a/tensorflow/contrib/model_pruning/python/pruning_test.py b/tensorflow/contrib/model_pruning/python/pruning_test.py
index f80b7c52c0..5b67656e9f 100644
--- a/tensorflow/contrib/model_pruning/python/pruning_test.py
+++ b/tensorflow/contrib/model_pruning/python/pruning_test.py
@@ -35,8 +35,8 @@ from tensorflow.python.training import training_util
class PruningHParamsTest(test.TestCase):
PARAM_LIST = [
"name=test", "threshold_decay=0.9", "pruning_frequency=10",
- "do_not_prune=[conv1,conv2]", "sparsity_function_end_step=100",
- "target_sparsity=0.9"
+ "sparsity_function_end_step=100", "target_sparsity=0.9",
+ "weight_sparsity_map=[conv1:0.8,conv2/kernel:0.8]"
]
TEST_HPARAMS = ",".join(PARAM_LIST)
@@ -55,9 +55,11 @@ class PruningHParamsTest(test.TestCase):
self.assertEqual(p._spec.name, "test")
self.assertAlmostEqual(p._spec.threshold_decay, 0.9)
self.assertEqual(p._spec.pruning_frequency, 10)
- self.assertAllEqual(p._spec.do_not_prune, ["conv1", "conv2"])
self.assertEqual(p._spec.sparsity_function_end_step, 100)
self.assertAlmostEqual(p._spec.target_sparsity, 0.9)
+ self.assertEqual(p._weight_sparsity_map["conv1"], 0.8)
+ self.assertEqual(p._weight_sparsity_map["conv2/kernel"], 0.8)
+
def testInitWithExternalSparsity(self):
with self.test_session():
@@ -211,6 +213,37 @@ class PruningTest(test.TestCase):
expected_non_zero_count = [100, 100, 80, 80, 60, 60, 40, 40, 40, 40]
self.assertAllEqual(expected_non_zero_count, non_zero_count)
+ def testWeightSpecificSparsity(self):
+ param_list = [
+ "begin_pruning_step=1", "pruning_frequency=1", "end_pruning_step=100",
+ "target_sparsity=0.5", "weight_sparsity_map=[layer2/weights:0.75]",
+ "threshold_decay=0.0"
+ ]
+ test_spec = ",".join(param_list)
+ pruning_hparams = pruning.get_pruning_hparams().parse(test_spec)
+
+ with variable_scope.variable_scope("layer1"):
+ w1 = variables.Variable(
+ math_ops.linspace(1.0, 100.0, 100), name="weights")
+ _ = pruning.apply_mask(w1)
+ with variable_scope.variable_scope("layer2"):
+ w2 = variables.Variable(
+ math_ops.linspace(1.0, 100.0, 100), name="weights")
+ _ = pruning.apply_mask(w2)
+
+ p = pruning.Pruning(pruning_hparams)
+ mask_update_op = p.conditional_mask_update_op()
+ increment_global_step = state_ops.assign_add(self.global_step, 1)
+
+ with self.test_session() as session:
+ variables.global_variables_initializer().run()
+ for _ in range(110):
+ session.run(mask_update_op)
+ session.run(increment_global_step)
+
+ self.assertAllEqual(
+ session.run(pruning.get_weight_sparsity()), [0.5, 0.75])
+
if __name__ == "__main__":
test.main()
diff --git a/tensorflow/contrib/opt/BUILD b/tensorflow/contrib/opt/BUILD
index bbdf962d04..280d4a5492 100644
--- a/tensorflow/contrib/opt/BUILD
+++ b/tensorflow/contrib/opt/BUILD
@@ -27,6 +27,7 @@ py_library(
"python/training/nadam_optimizer.py",
"python/training/powersign.py",
"python/training/reg_adagrad_optimizer.py",
+ "python/training/shampoo.py",
"python/training/sign_decay.py",
"python/training/variable_clipping_optimizer.py",
"python/training/weight_decay_optimizers.py",
@@ -344,3 +345,21 @@ py_test(
"//third_party/py/numpy",
],
)
+
+py_test(
+ name = "shampoo_test",
+ srcs = ["python/training/shampoo_test.py"],
+ srcs_version = "PY2AND3",
+ deps = [
+ ":opt_py",
+ "//tensorflow/python:array_ops",
+ "//tensorflow/python:client_testlib",
+ "//tensorflow/python:framework",
+ "//tensorflow/python:math_ops",
+ "//tensorflow/python:platform",
+ "//tensorflow/python:platform_test",
+ "//tensorflow/python:resource_variable_ops",
+ "//tensorflow/python:variables",
+ "//third_party/py/numpy",
+ ],
+)
diff --git a/tensorflow/contrib/opt/__init__.py b/tensorflow/contrib/opt/__init__.py
index 3e63e99030..9471fb0181 100644
--- a/tensorflow/contrib/opt/__init__.py
+++ b/tensorflow/contrib/opt/__init__.py
@@ -30,10 +30,10 @@ from tensorflow.contrib.opt.python.training.model_average_optimizer import *
from tensorflow.contrib.opt.python.training.moving_average_optimizer import *
from tensorflow.contrib.opt.python.training.multitask_optimizer_wrapper import *
from tensorflow.contrib.opt.python.training.nadam_optimizer import *
+from tensorflow.contrib.opt.python.training.shampoo import *
from tensorflow.contrib.opt.python.training.weight_decay_optimizers import *
from tensorflow.contrib.opt.python.training.powersign import *
from tensorflow.contrib.opt.python.training.variable_clipping_optimizer import *
-from tensorflow.contrib.opt.python.training.weight_decay_optimizers import *
# pylint: enable=wildcard-import
from tensorflow.python.util.all_util import remove_undocumented
@@ -62,6 +62,7 @@ _allowed_symbols = [
'ModelAverageOptimizer',
'ModelAverageCustomGetter',
'GGTOptimizer',
+ 'ShampooOptimizer',
]
remove_undocumented(__name__, _allowed_symbols)
diff --git a/tensorflow/contrib/opt/python/training/shampoo.py b/tensorflow/contrib/opt/python/training/shampoo.py
new file mode 100644
index 0000000000..7afa0998f4
--- /dev/null
+++ b/tensorflow/contrib/opt/python/training/shampoo.py
@@ -0,0 +1,463 @@
+# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+
+"""The Shampoo Optimizer.
+
+Variant of Adagrad using one preconditioner matrix per variable dimension.
+For details, see https://arxiv.org/abs/1802.09568
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import numpy as np
+from tensorflow.python.framework import ops
+from tensorflow.python.ops import array_ops
+from tensorflow.python.ops import control_flow_ops
+from tensorflow.python.ops import linalg_ops
+from tensorflow.python.ops import math_ops
+from tensorflow.python.ops import state_ops
+from tensorflow.python.platform import tf_logging
+from tensorflow.python.training import optimizer
+
+
+def GetParam(var, timestep):
+ if callable(var):
+ return var(timestep)
+ else:
+ return var
+
+
+class ShampooOptimizer(optimizer.Optimizer):
+ """The Shampoo Optimizer
+
+ Variant of Adagrad using one preconditioner matrix per variable dimension.
+ For details, see https://arxiv.org/abs/1802.09568
+
+ gbar is time-weighted accumulated gradient:
+ gbar[t] = gbar_decay[t] * gbar[t-1] + gbar_weight[t] * g[t]
+
+ mat_gbar is time-weighted accumulated gradient square:
+ mat_gbar_j[t] = mat_gbar_decay[t] * mat_gbar_j[t-1]
+ + mat_gbar_weight[t] * gg_j[t]
+ where if g[t] = g_abcd then gg_a[t] = g_abcd g_a'bcd (Einstein notation)
+
+ Update rule:
+ w[t+1] = w[t] - learning_rate[t] * Prod_j mat_gbar_j[t]^(-alpha/n) gbar[t]
+ Again, mat_gbar_j[t]^(-alpha) gbar[t] is a tensor contraction along the
+ j'th dimension of gbar[t] with the first dimension of
+ mat_gbar_j[t]^(-alpha/n), where alpha is a hyperparameter,
+ and n = rank of the variable.
+ Prod_j represents doing this contraction for all j in 0..n-1.
+
+ Typically learning_rate is constant, but could be time dependent by passing
+ a lambda function that depends on step.
+ """
+
+ def __init__(self, global_step=0,
+ max_matrix_size=500,
+ gbar_decay=0.0,
+ gbar_weight=1.0,
+ mat_gbar_decay=1.0,
+ mat_gbar_weight=1.0,
+ learning_rate=1.0,
+ svd_interval=1,
+ precond_update_interval=1,
+ epsilon=0.1,
+ alpha=0.5,
+ use_iterative_root=False,
+ use_locking=False,
+ name="Shampoo"):
+ """Default values of the various hyper-parameters.
+
+ gbar_decay, gbar_weight etc. can be a float or a time varying parameter.
+ For time-varying parameters use e.g. "lambda T: T / (T + 1.0)"
+ where the expression in the lambda is a tensorflow expression
+
+ Args:
+ global_step: tensorflow variable indicating the step.
+ max_matrix_size: We do not perform SVD for matrices larger than this.
+ gbar_decay:
+ gbar_weight: Used to update gbar:
+ gbar[t] = gbar_decay[t] * gbar[t-1] + gbar_weight[t] * g[t]
+ mat_gbar_decay:
+ mat_gbar_weight: Used to update mat_gbar:
+ mat_gbar_j[t] = mat_gbar_decay[t] * mat_gbar_j[t-1]
+ + mat_gbar_weight[t] * gg_j[t]
+ learning_rate: Similar to SGD
+ svd_interval: We should do SVD after this many steps. Default = 1, i.e.
+ every step. Usually 20 leads to no loss of accuracy, and
+ 50 or 100 is also OK. May also want more often early,
+ and less often later - set in caller as for example:
+ "svd_interval = lambda(T): tf.cond(
+ T < 2000, lambda: 20.0, lambda: 1000.0)"
+ precond_update_interval: We should update the preconditioners after
+ this many steps. Default = 1. Usually less than
+ svd_interval.
+ epsilon: epsilon * I_n is added to each mat_gbar_j for stability
+ alpha: total power of the preconditioners.
+ use_iterative_root: should the optimizer use SVD (faster) or the
+ iterative root method (for TPU) for finding the
+ roots of PSD matrices.
+ use_locking:
+ name: name of optimizer.
+ """
+
+ super(ShampooOptimizer, self).__init__(use_locking, name)
+
+ self._global_step = math_ops.to_float(global_step)
+ self._max_matrix_size = max_matrix_size
+ self._gbar_decay = gbar_decay
+ self._gbar_weight = gbar_weight
+ self._mat_gbar_decay = mat_gbar_decay
+ self._mat_gbar_weight = mat_gbar_weight
+ self._learning_rate = learning_rate
+ self._svd_interval = svd_interval
+ self._precond_update_interval = precond_update_interval
+ self._epsilon = epsilon
+ self._alpha = alpha
+ self._use_iterative_root = use_iterative_root
+ self._name = name
+
+ def _create_slots(self, var_list):
+ for v in var_list:
+ with ops.colocate_with(v):
+ _ = self._zeros_slot(v, "gbar", self._name)
+ shape = np.array(v.get_shape())
+ for i, d in enumerate(shape):
+ d_tensor = ops.convert_to_tensor(d)
+ if d < self._max_matrix_size:
+ mat_g_init = array_ops.zeros_like(linalg_ops.eye(d_tensor))
+ if self._svd_interval > 1:
+ _ = self._get_or_make_slot(v, linalg_ops.eye(d_tensor),
+ "H_" + str(i), self._name)
+ else:
+ mat_g_init = array_ops.zeros([d_tensor])
+
+ _ = self._get_or_make_slot(v, mat_g_init, "Gbar_" + str(i),
+ self._name)
+
+ def _apply_dense(self, grad, var):
+ return self._apply_gradient(grad, var)
+
+ def _apply_sparse(self, grad, var):
+ if var.get_shape()[0] < self._max_matrix_size or self._gbar_decay != 0.0:
+ # The dimension is small enough, we can make the variable dense and
+ # do a dense update
+ dense_grad = array_ops.scatter_nd(
+ array_ops.expand_dims(grad.indices, axis=1),
+ grad.values, array_ops.shape(var, out_type=grad.indices.dtype))
+ return self._apply_gradient(dense_grad, var)
+ return self._apply_gradient(grad.values, var, grad.indices)
+
+ def _weighted_average(self, var, weight, weight_t, rest):
+ """Computes exponential weighted average: var = weight_t * var + rest.
+
+ Important to ensure that var does not occur in rest, otherwise
+ we can get race conditions in a distributed setting.
+
+ Args:
+ var: variable to be updated
+ weight: parameter to be checked. If it is a constant, we can optimize.
+ weight_t: current value of parameter, used for weighting
+ rest: the remaining tensor to be added
+
+ Returns:
+ updated variable.
+ """
+ if weight == 0.0:
+ return rest # no need to update var, we will never use it.
+ if weight == 1.0: # common case
+ return state_ops.assign_add(var, rest)
+ # The op below can cause race conditions in a distributed setting,
+ # since computing weight_t * var + rest can take some time, during
+ # which var may be set by another worker. To prevent this, it should
+ # be implemented as a C++ op.
+ return var.assign_add((weight_t - 1) * var + rest)
+
+ def _update_mat_g(self, mat_g, grad, axes, mat_gbar_decay,
+ mat_gbar_weight, i):
+ """Updates the cumulative outer products of the gradients.
+
+ Args:
+ mat_g: the matrix to be updated
+ grad: the gradient of the variable
+ axes: a list of k-1 integers 0 to k-1, except i
+ mat_gbar_decay: constant for weighted average:
+ mat_g = mat_g * decay + grad * weight
+ mat_gbar_weight: constant for weighted average
+ i: index of dimension to be updated.
+
+ Returns:
+ updated mat_g = mat_g * mat_gbar_decay + grad_outer * mat_gbar_weight
+
+ In Einstein notation if i = 0: grad_outer_aa'= g_abcd g_a'bcd
+ thus grad_outer is a matrix d_i x d_i, where d_i is the size of the
+ i'th dimension of g.
+ Alternate view: If mat_i(grad) is the flattening of grad to a
+ d_i x (d_1d_2...d_{i-1}d_{i+1}...d_k) matrix, then
+ grad_outer = mat_i(grad) mat_i(grad).transpose
+ """
+ grad_outer = math_ops.tensordot(grad, grad, axes=(axes, axes),
+ name="grad_outer_" + str(i))
+ return self._weighted_average(mat_g, self._mat_gbar_decay, mat_gbar_decay,
+ mat_gbar_weight * grad_outer)
+
+ def _compute_power_svd(self, var, mat_g, mat_g_size, alpha, mat_h_slot_name):
+ """Computes mat_h = mat_g^alpha using svd. mat_g is a symmetric PSD matrix.
+
+ Args:
+ var: the variable we are updating.
+ mat_g: the symmetric PSD matrix whose power it to be computed
+ mat_g_size: size of mat_g
+ alpha: a real number
+ mat_h_slot_name: name of slot to store the power, if needed.
+
+ Returns:
+ mat_h = mat_g^alpha
+
+ Stores mat_h in the appropriate slot, if it exists.
+ Note that mat_g is PSD. So we could use linalg_ops.self_adjoint_eig.
+ """
+ if mat_g_size == 1:
+ mat_h = math_ops.pow(mat_g + self._epsilon, alpha)
+ else:
+ damping = self._epsilon * linalg_ops.eye(math_ops.to_int32(mat_g_size))
+ diag_d, mat_u, mat_v = linalg_ops.svd(mat_g + damping, full_matrices=True)
+ mat_h = math_ops.matmul(
+ mat_v * math_ops.pow(math_ops.maximum(diag_d, self._epsilon), alpha),
+ array_ops.transpose(mat_u))
+ if mat_h_slot_name is not None:
+ return state_ops.assign(self.get_slot(var, mat_h_slot_name), mat_h)
+ return mat_h
+
+ def _compute_power_iter(self, var, mat_g, mat_g_size, alpha, mat_h_slot_name,
+ iter_count=100, epsilon=1e-6):
+ """Computes mat_g^alpha, where alpha = -1/p, p a positive integer.
+
+ We use an iterative Schur-Newton method from equation 3.2 on page 9 of:
+
+ A Schur-Newton Method for the Matrix p-th Root and its Inverse
+ by Chun-Hua Guo and Nicholas J. Higham
+ SIAM Journal on Matrix Analysis and Applications,
+ 2006, Vol. 28, No. 3 : pp. 788-804
+ https://pdfs.semanticscholar.org/0abe/7f77433cf5908bfe2b79aa91af881da83858.pdf
+
+ Args:
+ var: the variable we are updating.
+ mat_g: the symmetric PSD matrix whose power it to be computed
+ mat_g_size: size of mat_g.
+ alpha: exponent, must be -1/p for p a positive integer.
+ mat_h_slot_name: name of slot to store the power, if needed.
+ iter_count: Maximum number of iterations.
+ epsilon: accuracy indicator, useful for early termination.
+
+ Returns:
+ mat_g^alpha
+ """
+
+ identity = linalg_ops.eye(math_ops.to_int32(mat_g_size))
+
+ def MatPower(mat_m, p):
+ """Computes mat_m^p, for p a positive integer.
+
+ Power p is known at graph compile time, so no need for loop and cond.
+ Args:
+ mat_m: a square matrix
+ p: a positive integer
+
+ Returns:
+ mat_m^p
+ """
+ assert p == int(p) and p > 0
+ power = None
+ while p > 0:
+ if p % 2 == 1:
+ power = math_ops.matmul(mat_m, power) if power is not None else mat_m
+ p //= 2
+ mat_m = math_ops.matmul(mat_m, mat_m)
+ return power
+
+ def IterCondition(i, mat_m, _):
+ return math_ops.logical_and(
+ i < iter_count,
+ math_ops.reduce_max(math_ops.abs(mat_m - identity)) > epsilon)
+
+ def IterBody(i, mat_m, mat_x):
+ mat_m_i = (1 - alpha) * identity + alpha * mat_m
+ return (i + 1, math_ops.matmul(MatPower(mat_m_i, -1.0/alpha), mat_m),
+ math_ops.matmul(mat_x, mat_m_i))
+
+ if mat_g_size == 1:
+ mat_h = math_ops.pow(mat_g + self._epsilon, alpha)
+ else:
+ damped_mat_g = mat_g + self._epsilon * identity
+ z = (1 - 1/alpha) / (2 * linalg_ops.norm(damped_mat_g, ord=2))
+ # The best value for z is
+ # (1 - 1/alpha) * (c_max^{-alpha} - c_min^{-alpha}) /
+ # (c_max^{1-alpha} - c_min^{1-alpha})
+ # where c_max and c_min are the largest and smallest singular values of
+ # damped_mat_g.
+ # The above estimate assumes that c_max > c_min * 2^p. (p = -1/alpha)
+ # Can replace above line by the one below, but it is less accurate,
+ # hence needs more iterations to converge.
+ # z = (1 - 1/alpha) / math_ops.trace(damped_mat_g)
+ # If we want the method to always converge, use z = 1 / norm(damped_mat_g)
+ # or z = 1 / math_ops.trace(damped_mat_g), but these can result in many
+ # extra iterations.
+ _, _, mat_h = control_flow_ops.while_loop(
+ IterCondition, IterBody,
+ [0, damped_mat_g * z, identity * math_ops.pow(z, -alpha)])
+ if mat_h_slot_name is not None:
+ return state_ops.assign(self.get_slot(var, mat_h_slot_name), mat_h)
+ return mat_h
+
+ def _compute_power(self, var, mat_g, mat_g_size, alpha, mat_h_slot_name=None):
+ """Just a switch between the iterative power vs svd."""
+ if self._use_iterative_root:
+ return self._compute_power_iter(var, mat_g, mat_g_size, alpha,
+ mat_h_slot_name)
+ else:
+ return self._compute_power_svd(var, mat_g, mat_g_size, alpha,
+ mat_h_slot_name)
+
+ def _apply_gradient(self, grad, var, indices=None):
+ """The main function to update a variable.
+
+ Args:
+ grad: A Tensor containing gradient to apply.
+ var: A Tensor containing the variable to update.
+ indices: An array of integers, for sparse update.
+
+ Returns:
+ Updated variable var = var - learning_rate * preconditioner * grad
+
+ If the gradient is dense, var and grad have the same shape.
+ If the update is sparse, then the first dimension of the gradient and var
+ may differ, others are all the same. In this case the indices array
+ provides the set of indices of the variable which are to be updated with
+ each row of the gradient.
+ """
+ global_step = self._global_step + 1
+
+ # Update accumulated weighted average of gradients
+ gbar = self.get_slot(var, "gbar")
+ gbar_decay_t = GetParam(self._gbar_decay, global_step)
+ gbar_weight_t = GetParam(self._gbar_weight, global_step)
+ if indices is not None:
+ # Note - the sparse update is not easily implemented, since the
+ # algorithm needs all indices of gbar to be updated
+ # if mat_gbar_decay != 1 or mat_gbar_decay != 0.
+ # One way to make mat_gbar_decay = 1 is by rescaling.
+ # If we want the update:
+ # G_{t+1} = a_{t+1} G_t + b_{t+1} w_t
+ # define:
+ # r_{t+1} = a_{t+1} * r_t
+ # h_t = G_t / r_t
+ # Then:
+ # h_{t+1} = h_t + (b_{t+1} / r_{t+1}) * w_t
+ # So we get the mat_gbar_decay = 1 as desired.
+ # We can implement this in a future version as needed.
+ # However we still need gbar_decay = 0, otherwise all indices
+ # of the variable will need to be updated.
+ if self._gbar_decay != 0.0:
+ tf_logging.warning("Not applying momentum for variable: %s" % var.name)
+ gbar_updated = grad
+ else:
+ gbar_updated = self._weighted_average(gbar, self._gbar_decay,
+ gbar_decay_t,
+ gbar_weight_t * grad)
+
+ # Update the preconditioners and compute the preconditioned gradient
+ shape = var.get_shape()
+ mat_g_list = []
+ for i in range(len(shape)):
+ mat_g_list.append(self.get_slot(var, "Gbar_" + str(i)))
+ mat_gbar_decay_t = GetParam(self._mat_gbar_decay, global_step)
+ mat_gbar_weight_t = GetParam(self._mat_gbar_weight, global_step)
+
+ preconditioned_grad = gbar_updated
+ v_rank = len(mat_g_list)
+ neg_alpha = - GetParam(self._alpha, global_step) / v_rank
+ svd_interval = GetParam(self._svd_interval, global_step)
+ precond_update_interval = GetParam(self._precond_update_interval,
+ global_step)
+ for i, mat_g in enumerate(mat_g_list):
+ # axes is the list of indices to reduce - everything but the current i.
+ axes = list(range(i)) + list(range(i+1, v_rank))
+ if shape[i] < self._max_matrix_size:
+ # If the tensor size is sufficiently small perform full Shampoo update
+ # Note if precond_update_interval > 1 and mat_gbar_decay_t != 1, this
+ # is not strictly correct. However we will use it for now, and
+ # fix if needed. (G_1 = aG + bg ==> G_n = a^n G + (1+a+..+a^{n-1})bg)
+
+ # pylint: disable=g-long-lambda,cell-var-from-loop
+ mat_g_updated = control_flow_ops.cond(
+ math_ops.mod(global_step, precond_update_interval) < 1,
+ lambda: self._update_mat_g(
+ mat_g, grad, axes, mat_gbar_decay_t,
+ mat_gbar_weight_t * precond_update_interval, i),
+ lambda: mat_g)
+
+ if self._svd_interval == 1:
+ mat_h = self._compute_power(var, mat_g_updated, shape[i], neg_alpha)
+ else:
+ mat_h = control_flow_ops.cond(
+ math_ops.mod(global_step, svd_interval) < 1,
+ lambda: self._compute_power(var, mat_g_updated, shape[i],
+ neg_alpha, "H_" + str(i)),
+ lambda: self.get_slot(var, "H_" + str(i)))
+
+ # mat_h is a square matrix of size d_i x d_i
+ # preconditioned_grad is a d_i x ... x d_n x d_0 x ... d_{i-1} tensor
+ # After contraction with a d_i x d_i tensor
+ # it becomes a d_{i+1} x ... x d_n x d_0 x ... d_i tensor
+ # (the first dimension is contracted out, and the second dimension of
+ # mat_h is appended). After going through all the indices, it becomes
+ # a d_0 x ... x d_n tensor again.
+ preconditioned_grad = math_ops.tensordot(preconditioned_grad, mat_h,
+ axes=([0], [0]),
+ name="precond_" + str(i))
+ else:
+ # Tensor size is too large -- perform diagonal Shampoo update
+ grad_outer = math_ops.reduce_sum(grad * grad, axis=axes)
+ if i == 0 and indices is not None:
+ assert self._mat_gbar_decay == 1.0
+ mat_g_updated = state_ops.scatter_add(mat_g, indices,
+ mat_gbar_weight_t * grad_outer)
+ mat_h = math_ops.pow(
+ array_ops.gather(mat_g_updated, indices) + self._epsilon,
+ neg_alpha)
+ else:
+ mat_g_updated = self._weighted_average(mat_g,
+ self._mat_gbar_decay,
+ mat_gbar_decay_t,
+ mat_gbar_weight_t * grad_outer)
+ mat_h = math_ops.pow(mat_g_updated + self._epsilon, neg_alpha)
+
+ # Need to do the transpose to ensure that the tensor becomes
+ # a d_{i+1} x ... x d_n x d_0 x ... d_i tensor as described above.
+ preconditioned_grad = array_ops.transpose(
+ preconditioned_grad, perm=list(range(1, v_rank)) + [0]) * mat_h
+
+ # Update the variable based on the Shampoo update
+ learning_rate_t = GetParam(self._learning_rate, global_step)
+ if indices is not None:
+ var_updated = state_ops.scatter_sub(var, indices,
+ learning_rate_t * preconditioned_grad)
+ else:
+ var_updated = state_ops.assign_sub(var,
+ learning_rate_t * preconditioned_grad)
+ return var_updated
diff --git a/tensorflow/contrib/opt/python/training/shampoo_test.py b/tensorflow/contrib/opt/python/training/shampoo_test.py
new file mode 100644
index 0000000000..3148d02296
--- /dev/null
+++ b/tensorflow/contrib/opt/python/training/shampoo_test.py
@@ -0,0 +1,669 @@
+# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+
+"""Functional tests for AdaMoo optimizer."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import numpy as np
+
+from tensorflow.contrib.opt.python.training import shampoo
+from tensorflow.python.framework import constant_op
+from tensorflow.python.framework import dtypes
+from tensorflow.python.framework import ops
+from tensorflow.python.ops import array_ops
+from tensorflow.python.ops import variables
+from tensorflow.python.platform import test
+
+TOLERANCE = 1e-3
+
+
+def np_power(mat_g, alpha):
+ """Computes mat_g^alpha for a square symmetric matrix mat_g."""
+
+ mat_u, diag_d, mat_v = np.linalg.svd(mat_g)
+ diag_d = np.power(diag_d, alpha)
+ return np.dot(np.dot(mat_u, np.diag(diag_d)), mat_v)
+
+
+class ShampooTest(test.TestCase):
+
+ def testBasicVector(self):
+ """Similar to the full Adagrad update."""
+
+ size = 20
+ init_var_np = np.zeros(size)
+ grad_np = np.random.rand(size)
+ grad_np_2 = np.random.rand(size)
+
+ with self.test_session() as sess:
+ global_step = variables.Variable(0, dtype=dtypes.int64)
+ var = variables.Variable(init_var_np, dtype=dtypes.float32)
+ grad = constant_op.constant(grad_np, dtype=dtypes.float32)
+ grad_2 = constant_op.constant(grad_np_2, dtype=dtypes.float32)
+
+ opt = shampoo.ShampooOptimizer(global_step)
+ update = opt.apply_gradients(zip([grad], [var]),
+ global_step=global_step)
+ update_2 = opt.apply_gradients(zip([grad_2], [var]),
+ global_step=global_step)
+ variables.global_variables_initializer().run()
+
+ init_val = sess.run(var)
+ self.assertAllCloseAccordingToType(init_var_np, init_val)
+
+ # Run a step of Shampoo
+ update.run()
+ new_val = sess.run(var)
+
+ # let up compute this in numpy
+ # Update rule is var = var - lr * mat_g^{-0.5} * grad
+ # lr = 1
+ mat_g = np.outer(grad_np, grad_np)
+ mat_h = np_power(mat_g + 0.1 * np.eye(size), -0.5)
+ new_val_np = init_var_np - np.dot(mat_h, grad_np)
+
+ self.assertAllCloseAccordingToType(new_val_np, new_val,
+ atol=TOLERANCE, rtol=TOLERANCE)
+
+ # Run another step of Shampoo
+ update_2.run()
+ new_val = sess.run(var)
+
+ mat_g += np.outer(grad_np_2, grad_np_2)
+ mat_h = np_power(mat_g + 0.1 * np.eye(size), -0.5)
+ new_val_np -= np.dot(mat_h, grad_np_2)
+
+ self.assertAllCloseAccordingToType(new_val_np, new_val,
+ atol=TOLERANCE, rtol=TOLERANCE)
+
+ def testBasicMatrix(self):
+ """Check update when gradient is a matrix."""
+ size = [10, 5]
+ init_var_np = np.zeros(size)
+ grad_np = np.random.rand(size[0], size[1])
+ grad_np_2 = np.random.rand(size[0], size[1])
+
+ with self.test_session() as sess:
+ global_step = variables.Variable(0, dtype=dtypes.int64)
+ var = variables.Variable(init_var_np, dtype=dtypes.float32)
+ grad = constant_op.constant(grad_np, dtype=dtypes.float32)
+ grad_2 = constant_op.constant(grad_np_2, dtype=dtypes.float32)
+
+ opt = shampoo.ShampooOptimizer(global_step)
+ update = opt.apply_gradients(zip([grad], [var]),
+ global_step=global_step)
+ update_2 = opt.apply_gradients(zip([grad_2], [var]),
+ global_step=global_step)
+ variables.global_variables_initializer().run()
+
+ init_val = sess.run(var)
+ self.assertAllCloseAccordingToType(init_var_np, init_val)
+
+ # Run a step of Shampoo
+ update.run()
+ new_val = sess.run(var)
+
+ # let up compute this in numpy
+ # Update rule is var = var - lr * mat_g1^{-0.25} * grad * mat_g2^{-0.25}
+ # lr = 1
+ mat_g1 = np.dot(grad_np, grad_np.transpose())
+ mat_left = np_power(mat_g1 + 0.1 * np.eye(size[0]), -0.25)
+ mat_g2 = np.dot(grad_np.transpose(), grad_np)
+ mat_right = np_power(mat_g2 + 0.1 * np.eye(size[1]), -0.25)
+ new_val_np = init_var_np - np.dot(np.dot(mat_left, grad_np), mat_right)
+
+ self.assertAllCloseAccordingToType(new_val_np, new_val,
+ atol=TOLERANCE, rtol=TOLERANCE)
+
+ # Run another step of Shampoo
+ update_2.run()
+ new_val = sess.run(var)
+
+ mat_g1 += np.dot(grad_np_2, grad_np_2.transpose())
+ mat_left = np_power(mat_g1 + 0.1 * np.eye(size[0]), -0.25)
+ mat_g2 += np.dot(grad_np_2.transpose(), grad_np_2)
+ mat_right = np_power(mat_g2 + 0.1 * np.eye(size[1]), -0.25)
+ new_val_np -= np.dot(np.dot(mat_left, grad_np_2), mat_right)
+
+ self.assertAllCloseAccordingToType(new_val_np, new_val,
+ atol=TOLERANCE, rtol=TOLERANCE)
+
+ def _testBasicTensor(self, use_iterative_root):
+ """Check update when gradient is a tensor."""
+ size = [10, 5, 7]
+ init_var_np = np.zeros(size)
+ grad_np = np.random.rand(size[0], size[1], size[2])
+ grad_np_2 = np.random.rand(size[0], size[1], size[2])
+
+ with self.test_session() as sess:
+ global_step = variables.Variable(0, dtype=dtypes.int64)
+ var = variables.Variable(init_var_np, dtype=dtypes.float32)
+ grad = constant_op.constant(grad_np, dtype=dtypes.float32)
+ grad_2 = constant_op.constant(grad_np_2, dtype=dtypes.float32)
+
+ opt = shampoo.ShampooOptimizer(global_step,
+ use_iterative_root=use_iterative_root)
+ update = opt.apply_gradients(zip([grad], [var]),
+ global_step=global_step)
+ update_2 = opt.apply_gradients(zip([grad_2], [var]),
+ global_step=global_step)
+ variables.global_variables_initializer().run()
+
+ init_val = sess.run(var)
+ self.assertAllCloseAccordingToType(init_var_np, init_val)
+
+ # Run a step of Shampoo
+ update.run()
+ new_val = sess.run(var)
+
+ # let up compute this in numpy
+ # Update rule is var = var - lr * Prod_i mat_g_i^{-0.5/3} grad
+ # lr = 1
+ mat_g1 = np.tensordot(grad_np, grad_np, axes=([1, 2], [1, 2]))
+ mat_g1_a = np_power(mat_g1 + 0.1 * np.eye(size[0]), -0.5/3.0)
+ mat_g2 = np.tensordot(grad_np, grad_np, axes=([0, 2], [0, 2]))
+ mat_g2_a = np_power(mat_g2 + 0.1 * np.eye(size[1]), -0.5/3.0)
+ mat_g3 = np.tensordot(grad_np, grad_np, axes=([0, 1], [0, 1]))
+ mat_g3_a = np_power(mat_g3 + 0.1 * np.eye(size[2]), -0.5/3.0)
+
+ precond_grad = np.tensordot(grad_np, mat_g1_a, axes=([0], [0]))
+ precond_grad = np.tensordot(precond_grad, mat_g2_a, axes=([0], [0]))
+ precond_grad = np.tensordot(precond_grad, mat_g3_a, axes=([0], [0]))
+ new_val_np = init_var_np - precond_grad
+
+ self.assertAllCloseAccordingToType(new_val_np, new_val,
+ atol=TOLERANCE, rtol=TOLERANCE)
+
+ # Run another step of Shampoo
+ update_2.run()
+ new_val = sess.run(var)
+
+ mat_g1 += np.tensordot(grad_np_2, grad_np_2, axes=([1, 2], [1, 2]))
+ mat_g1_a = np_power(mat_g1 + 0.1 * np.eye(size[0]), -0.5/3.0)
+ mat_g2 += np.tensordot(grad_np_2, grad_np_2, axes=([0, 2], [0, 2]))
+ mat_g2_a = np_power(mat_g2 + 0.1 * np.eye(size[1]), -0.5/3.0)
+ mat_g3 += np.tensordot(grad_np_2, grad_np_2, axes=([0, 1], [0, 1]))
+ mat_g3_a = np_power(mat_g3 + 0.1 * np.eye(size[2]), -0.5/3.0)
+
+ precond_grad = np.tensordot(grad_np_2, mat_g1_a, axes=([0], [0]))
+ precond_grad = np.tensordot(precond_grad, mat_g2_a, axes=([0], [0]))
+ precond_grad = np.tensordot(precond_grad, mat_g3_a, axes=([0], [0]))
+ new_val_np -= precond_grad
+
+ self.assertAllCloseAccordingToType(new_val_np, new_val,
+ atol=TOLERANCE, rtol=TOLERANCE)
+
+ def testBasicTensor(self):
+ for use_iterative_root in [True, False]:
+ self._testBasicTensor(use_iterative_root)
+
+ def testLargeVector(self):
+ """This is just the diagonal Adagrad update."""
+
+ size = 2000
+ init_var_np = np.zeros(size)
+ grad_np = np.random.rand(size)
+ grad_np_2 = np.random.rand(size)
+
+ with self.test_session() as sess:
+ global_step = variables.Variable(0, dtype=dtypes.int64)
+ var = variables.Variable(init_var_np, dtype=dtypes.float32)
+ grad = constant_op.constant(grad_np, dtype=dtypes.float32)
+ grad_2 = constant_op.constant(grad_np_2, dtype=dtypes.float32)
+
+ opt = shampoo.ShampooOptimizer(global_step)
+ update = opt.apply_gradients(zip([grad], [var]),
+ global_step=global_step)
+ update_2 = opt.apply_gradients(zip([grad_2], [var]),
+ global_step=global_step)
+ variables.global_variables_initializer().run()
+
+ init_val = sess.run(var)
+ self.assertAllCloseAccordingToType(init_var_np, init_val)
+
+ # Run a step of Shampoo
+ update.run()
+ new_val = sess.run(var)
+
+ # let up compute this in numpy
+ # Update rule is var = var - lr * gg^{-0.5} * grad
+ # lr = 1
+ mat_g = grad_np * grad_np + 0.1
+ new_val_np = init_var_np - np.power(mat_g, -0.5) * grad_np
+
+ self.assertAllCloseAccordingToType(new_val_np, new_val)
+
+ # Run another step of Shampoo
+ update_2.run()
+ new_val = sess.run(var)
+
+ mat_g += grad_np_2 * grad_np_2
+ new_val_np -= np.power(mat_g, -0.5) * grad_np_2
+
+ self.assertAllCloseAccordingToType(new_val_np, new_val)
+
+ def testLargeMatrix(self):
+ """Gradient is a matrix, one of whose dimensions is large.
+
+ We do diagonal updates for large dimensions.
+ """
+
+ size = [2000, 3]
+ init_var_np = np.zeros(size)
+ grad_np = np.random.rand(size[0], size[1])
+ grad_np_2 = np.random.rand(size[0], size[1])
+
+ with self.test_session() as sess:
+ global_step = variables.Variable(0, dtype=dtypes.int64)
+ var = variables.Variable(init_var_np, dtype=dtypes.float32)
+ grad = constant_op.constant(grad_np, dtype=dtypes.float32)
+ grad_2 = constant_op.constant(grad_np_2, dtype=dtypes.float32)
+
+ opt = shampoo.ShampooOptimizer(global_step)
+ update = opt.apply_gradients(zip([grad], [var]),
+ global_step=global_step)
+ update_2 = opt.apply_gradients(zip([grad_2], [var]),
+ global_step=global_step)
+ variables.global_variables_initializer().run()
+
+ init_val = sess.run(var)
+ self.assertAllCloseAccordingToType(init_var_np, init_val)
+
+ # Run a step of Shampoo
+ update.run()
+ new_val = sess.run(var)
+
+ # let up compute this in numpy
+ # Update rule is var = var - lr * mat_left * grad * mat_right
+ # where the mat_left * grad is just element-wise product,
+ # with broadcasting
+ # lr = 1
+
+ mat_g1 = np.sum(grad_np * grad_np, axis=1, keepdims=True)
+ mat_left = np.power(mat_g1 + 0.1, -0.25)
+ mat_g2 = np.dot(grad_np.transpose(), grad_np)
+ mat_right = np_power(mat_g2 + 0.1 * np.eye(size[1]), -0.25)
+ new_val_np = init_var_np - np.dot(grad_np * mat_left, mat_right)
+
+ self.assertAllCloseAccordingToType(new_val_np, new_val,
+ atol=TOLERANCE, rtol=TOLERANCE)
+
+ # Run another step of Shampoo
+ update_2.run()
+ new_val = sess.run(var)
+
+ mat_g1 += np.sum(grad_np_2 * grad_np_2, axis=1, keepdims=True)
+ mat_left = np.power(mat_g1 + 0.1, -0.25)
+ mat_g2 += np.dot(grad_np_2.transpose(), grad_np_2)
+ mat_right = np_power(mat_g2 + 0.1 * np.eye(size[1]), -0.25)
+ new_val_np -= np.dot(grad_np_2 * mat_left, mat_right)
+
+ self.assertAllCloseAccordingToType(new_val_np, new_val,
+ atol=TOLERANCE, rtol=TOLERANCE)
+
+ def testSparseUpdateLarge(self):
+ """Check update when gradient is of type IndexSlices.
+
+ We do diagonal updates for the first dimension, unless it is very small.
+ """
+
+ size = [2000, 3]
+ sample_size_1 = 100
+ init_var_np = np.zeros(size)
+ grad_indices = np.sort(np.random.choice(np.arange(size[0]), sample_size_1,
+ replace=False))
+ grad_np = np.random.rand(sample_size_1, size[1])
+
+ sample_size_2 = 7
+ grad_indices_2 = np.sort(np.random.choice(np.arange(size[0]), sample_size_2,
+ replace=False))
+ grad_np_2 = np.random.rand(sample_size_2, size[1])
+
+ with self.test_session() as sess:
+ global_step = variables.Variable(0, dtype=dtypes.int64)
+ var = variables.Variable(init_var_np, dtype=dtypes.float32)
+ grad = ops.IndexedSlices(
+ constant_op.constant(grad_np, dtype=dtypes.float32),
+ constant_op.constant(grad_indices),
+ constant_op.constant(size))
+ grad_2 = ops.IndexedSlices(
+ constant_op.constant(grad_np_2, dtype=dtypes.float32),
+ constant_op.constant(grad_indices_2),
+ constant_op.constant(size))
+
+ opt = shampoo.ShampooOptimizer(global_step)
+ update = opt.apply_gradients(zip([grad], [var]),
+ global_step=global_step)
+ update_2 = opt.apply_gradients(zip([grad_2], [var]),
+ global_step=global_step)
+ variables.global_variables_initializer().run()
+
+ init_val = sess.run(var)
+ self.assertAllCloseAccordingToType(init_var_np, init_val)
+
+ # Run a step of Shampoo
+ update.run()
+ new_val = sess.run(var)
+
+ # let up compute this in numpy
+ # Update rule is var = var - lr * mat_left * grad * mat_right
+ # where the mat_left * grad is just element-wise product,
+ # with broadcasting
+ # lr = 1
+ # In this case the update lr * mat_left * grad * mat_right is
+ # of size 10 x 2.
+ # So the correct indices of var need to be updated.
+
+ mat_g1 = np.sum(grad_np * grad_np, axis=1, keepdims=True)
+ mat_g1_acc = np.zeros((size[0], 1))
+ mat_g1_acc[grad_indices] += mat_g1
+ mat_left = np.power(mat_g1 + 0.1, -0.25)
+ mat_g2 = np.dot(grad_np.transpose(), grad_np)
+ mat_right = np_power(mat_g2 + 0.1 * np.eye(size[1]), -0.25)
+ new_val_np = init_var_np
+ new_val_np[grad_indices, :] -= np.dot(grad_np * mat_left, mat_right)
+
+ self.assertAllCloseAccordingToType(new_val_np, new_val,
+ atol=TOLERANCE, rtol=TOLERANCE)
+
+ # Run another step of Shampoo
+ update_2.run()
+ new_val = sess.run(var)
+
+ mat_g1 = np.sum(grad_np_2 * grad_np_2, axis=1, keepdims=True)
+ mat_g1_acc[grad_indices_2] += mat_g1
+ mat_left = np.power(mat_g1_acc[grad_indices_2] + 0.1, -0.25)
+ mat_g2 += np.dot(grad_np_2.transpose(), grad_np_2)
+ mat_right = np_power(mat_g2 + 0.1 * np.eye(size[1]), -0.25)
+ new_val_np[grad_indices_2, :] -= np.dot(grad_np_2 * mat_left, mat_right)
+
+ self.assertAllCloseAccordingToType(new_val_np, new_val,
+ atol=TOLERANCE, rtol=TOLERANCE)
+
+ def _testSparseUpdateSmall(self, use_iterative_root):
+ """Gradient is of type IndexSlices, but the first dimension is small.
+
+ We create dense gradient and do the full update with SVD etc.
+
+ Args:
+ use_iterative_root: use iterative power method or SVD to find nth roots.
+ """
+
+ size = [100, 3, 5]
+ sample_size = 10
+ init_var_np = np.zeros(size)
+ grad_indices = np.sort(np.random.choice(np.arange(size[0]), sample_size,
+ replace=False))
+ grad_np = np.random.rand(sample_size, size[1], size[2])
+
+ with self.test_session() as sess:
+ global_step = variables.Variable(0, dtype=dtypes.int64)
+ var = variables.Variable(init_var_np, dtype=dtypes.float32)
+ grad = ops.IndexedSlices(
+ constant_op.constant(grad_np, dtype=dtypes.float32),
+ constant_op.constant(grad_indices),
+ constant_op.constant(size))
+
+ opt = shampoo.ShampooOptimizer(global_step,
+ use_iterative_root=use_iterative_root)
+ update = opt.apply_gradients(zip([grad], [var]),
+ global_step=global_step)
+ variables.global_variables_initializer().run()
+
+ init_val = sess.run(var)
+ self.assertAllCloseAccordingToType(init_var_np, init_val)
+
+ # Run a step of Shampoo
+ update.run()
+ new_val = sess.run(var)
+
+ # let up compute this in numpy
+ # Update rule is var = var - lr * Prod_i mat_g_i^{-0.125} grad
+ # lr = 1
+ grad_dense = np.zeros_like(init_var_np)
+ grad_dense[grad_indices] = grad_np
+
+ mat_g1 = np.tensordot(grad_dense, grad_dense, axes=([1, 2], [1, 2]))
+ mat_g1_a = np_power(mat_g1 + 0.1 * np.eye(size[0]), -0.5/3.0)
+ mat_g2 = np.tensordot(grad_dense, grad_dense, axes=([0, 2], [0, 2]))
+ mat_g2_a = np_power(mat_g2 + 0.1 * np.eye(size[1]), -0.5/3.0)
+ mat_g3 = np.tensordot(grad_dense, grad_dense, axes=([0, 1], [0, 1]))
+ mat_g3_a = np_power(mat_g3 + 0.1 * np.eye(size[2]), -0.5/3.0)
+
+ precond_grad = np.tensordot(grad_dense, mat_g1_a, axes=([0], [0]))
+ precond_grad = np.tensordot(precond_grad, mat_g2_a, axes=([0], [0]))
+ precond_grad = np.tensordot(precond_grad, mat_g3_a, axes=([0], [0]))
+ new_val_np = init_var_np - precond_grad
+
+ self.assertAllCloseAccordingToType(new_val_np, new_val,
+ atol=TOLERANCE, rtol=TOLERANCE)
+
+ def testSparseUpdateSmall(self):
+ for use_iterative_root in [True, False]:
+ self._testSparseUpdateSmall(use_iterative_root)
+
+ def _testBasicTensorWithMomentum(self, use_iterative_root):
+ """Check update with momentum when gradient is a tensor.
+
+ Args:
+ use_iterative_root: use iterative power method or SVD to find nth roots.
+ """
+ size = [10, 5, 7]
+ init_var_np = np.zeros(size)
+ grad_np = np.random.rand(size[0], size[1], size[2])
+ grad_np_2 = np.random.rand(size[0], size[1], size[2])
+ gbar_decay = 0.9
+ gbar_weight = 0.1
+
+ with self.test_session() as sess:
+ global_step = variables.Variable(0, dtype=dtypes.int64)
+ var = variables.Variable(init_var_np, dtype=dtypes.float32)
+ grad = constant_op.constant(grad_np, dtype=dtypes.float32)
+ grad_2 = constant_op.constant(grad_np_2, dtype=dtypes.float32)
+
+ opt = shampoo.ShampooOptimizer(global_step, gbar_decay=gbar_decay,
+ gbar_weight=gbar_weight,
+ use_iterative_root=use_iterative_root)
+ update = opt.apply_gradients(zip([grad], [var]),
+ global_step=global_step)
+ update_2 = opt.apply_gradients(zip([grad_2], [var]),
+ global_step=global_step)
+ variables.global_variables_initializer().run()
+
+ # Run a step of Shampoo
+ update.run()
+ new_val = sess.run(var)
+
+ # let up compute this in numpy
+ # Update rule is var = var - lr * Prod_i mat_g_i^{-0.5/3} grad
+ # lr = 1
+ mat_g1 = np.tensordot(grad_np, grad_np, axes=([1, 2], [1, 2]))
+ mat_g1_a = np_power(mat_g1 + 0.1 * np.eye(size[0]), -0.5/3.0)
+ mat_g2 = np.tensordot(grad_np, grad_np, axes=([0, 2], [0, 2]))
+ mat_g2_a = np_power(mat_g2 + 0.1 * np.eye(size[1]), -0.5/3.0)
+ mat_g3 = np.tensordot(grad_np, grad_np, axes=([0, 1], [0, 1]))
+ mat_g3_a = np_power(mat_g3 + 0.1 * np.eye(size[2]), -0.5/3.0)
+
+ gbar_np = gbar_weight * grad_np
+ precond_grad = np.tensordot(gbar_np, mat_g1_a, axes=([0], [0]))
+ precond_grad = np.tensordot(precond_grad, mat_g2_a, axes=([0], [0]))
+ precond_grad = np.tensordot(precond_grad, mat_g3_a, axes=([0], [0]))
+ new_val_np = init_var_np - precond_grad
+
+ self.assertAllCloseAccordingToType(new_val_np, new_val,
+ atol=TOLERANCE, rtol=TOLERANCE)
+
+ # Run another step of Shampoo
+ update_2.run()
+ new_val = sess.run(var)
+
+ mat_g1 += np.tensordot(grad_np_2, grad_np_2, axes=([1, 2], [1, 2]))
+ mat_g1_a = np_power(mat_g1 + 0.1 * np.eye(size[0]), -0.5/3.0)
+ mat_g2 += np.tensordot(grad_np_2, grad_np_2, axes=([0, 2], [0, 2]))
+ mat_g2_a = np_power(mat_g2 + 0.1 * np.eye(size[1]), -0.5/3.0)
+ mat_g3 += np.tensordot(grad_np_2, grad_np_2, axes=([0, 1], [0, 1]))
+ mat_g3_a = np_power(mat_g3 + 0.1 * np.eye(size[2]), -0.5/3.0)
+
+ gbar_np_2 = gbar_decay * gbar_np + gbar_weight * grad_np_2
+ precond_grad = np.tensordot(gbar_np_2, mat_g1_a, axes=([0], [0]))
+ precond_grad = np.tensordot(precond_grad, mat_g2_a, axes=([0], [0]))
+ precond_grad = np.tensordot(precond_grad, mat_g3_a, axes=([0], [0]))
+ new_val_np -= precond_grad
+
+ self.assertAllCloseAccordingToType(new_val_np, new_val,
+ atol=TOLERANCE, rtol=TOLERANCE)
+
+ def testBasicTensorWithMomentum(self):
+ for use_iterative_root in [True, False]:
+ self._testBasicTensorWithMomentum(use_iterative_root)
+
+ def _testDelayedSVD(self, use_iterative_root):
+ """Performing the SVD every nth step.
+
+ Args:
+ use_iterative_root: use iterative power method or SVD to find nth roots.
+ """
+ size = [10, 5, 7]
+ init_var_np = np.zeros(size).astype(np.float32)
+ iterations = 20
+ svd_interval = 5
+ grad_np = np.random.rand(
+ iterations, size[0], size[1], size[2]).astype(np.float32)
+ mat_g1_a = np.eye(size[0])
+ mat_g1 = np.zeros_like(mat_g1_a)
+ mat_g2_a = np.eye(size[1])
+ mat_g2 = np.zeros_like(mat_g2_a)
+ mat_g3_a = np.eye(size[2])
+ mat_g3 = np.zeros_like(mat_g3_a)
+
+ with self.test_session() as sess:
+ global_step = variables.Variable(0, dtype=dtypes.int64)
+ var = variables.Variable(init_var_np, dtype=dtypes.float32)
+ grad = array_ops.placeholder(dtypes.float32, shape=size)
+
+ opt = shampoo.ShampooOptimizer(global_step, svd_interval=svd_interval,
+ use_iterative_root=use_iterative_root)
+ update = opt.apply_gradients(zip([grad], [var]),
+ global_step=global_step)
+ variables.global_variables_initializer().run()
+
+ init_val = sess.run(var)
+ self.assertAllCloseAccordingToType(init_var_np, init_val)
+ new_val_np = init_var_np
+
+ # Run n steps of Shampoo
+ for i in range(iterations):
+ _ = sess.run(update, feed_dict={grad: grad_np[i]})
+ new_val = sess.run(var)
+
+ # let up compute this in numpy
+ # Update rule is var = var - lr * Prod_i mat_g_i^{-0.5/3} grad
+ # lr = 1
+ mat_g1 += np.tensordot(grad_np[i], grad_np[i], axes=([1, 2], [1, 2]))
+ mat_g2 += np.tensordot(grad_np[i], grad_np[i], axes=([0, 2], [0, 2]))
+ mat_g3 += np.tensordot(grad_np[i], grad_np[i], axes=([0, 1], [0, 1]))
+ if (i + 1) % svd_interval == 0:
+ mat_g1_a = np_power(mat_g1 + 0.1 * np.eye(size[0]), -0.5/3.0)
+ mat_g2_a = np_power(mat_g2 + 0.1 * np.eye(size[1]), -0.5/3.0)
+ mat_g3_a = np_power(mat_g3 + 0.1 * np.eye(size[2]), -0.5/3.0)
+
+ precond_grad = np.tensordot(grad_np[i], mat_g1_a, axes=([0], [0]))
+ precond_grad = np.tensordot(precond_grad, mat_g2_a, axes=([0], [0]))
+ precond_grad = np.tensordot(precond_grad, mat_g3_a, axes=([0], [0]))
+ new_val_np -= precond_grad
+
+ self.assertAllCloseAccordingToType(new_val_np, new_val,
+ atol=TOLERANCE, rtol=TOLERANCE)
+
+ def testDelayedSVD(self):
+ for use_iterative_root in [True, False]:
+ self._testDelayedSVD(use_iterative_root)
+
+ def _testDelayedPrecondUpdate(self, use_iterative_root):
+ """Update the squared sum every nth step, drop the other steps.
+
+ Args:
+ use_iterative_root: use iterative power method or SVD to find nth roots.
+ """
+ size = [10, 5, 7]
+ init_var_np = np.zeros(size).astype(np.float32)
+ iterations = 100
+ grad_np = np.random.rand(
+ iterations, size[0], size[1], size[2]).astype(np.float32)
+ svd_interval = 20
+ precond_update_interval = 5
+ mat_g1_a = np.eye(size[0])
+ mat_g1 = np.zeros_like(mat_g1_a)
+ mat_g2_a = np.eye(size[1])
+ mat_g2 = np.zeros_like(mat_g2_a)
+ mat_g3_a = np.eye(size[2])
+ mat_g3 = np.zeros_like(mat_g3_a)
+
+ with self.test_session() as sess:
+ global_step = variables.Variable(0, dtype=dtypes.int64)
+ var = variables.Variable(init_var_np, dtype=dtypes.float32)
+ grad = array_ops.placeholder(dtypes.float32, shape=size)
+
+ opt = shampoo.ShampooOptimizer(
+ global_step, svd_interval=svd_interval,
+ precond_update_interval=precond_update_interval,
+ use_iterative_root=use_iterative_root)
+ update = opt.apply_gradients(zip([grad], [var]),
+ global_step=global_step)
+ variables.global_variables_initializer().run()
+
+ init_val = sess.run(var)
+ self.assertAllCloseAccordingToType(init_var_np, init_val)
+ new_val_np = init_var_np
+
+ # Run n steps of Shampoo
+ for i in range(iterations):
+ _ = sess.run(update, feed_dict={grad: grad_np[i]})
+ new_val = sess.run(var)
+
+ # let up compute this in numpy
+ # Update rule is var = var - lr * Prod_i mat_g_i^{-0.5/3} grad
+ # lr = 1
+ if (i + 1) % precond_update_interval == 0:
+ mat_g1 += (np.tensordot(grad_np[i], grad_np[i], axes=([1, 2], [1, 2]))
+ * precond_update_interval)
+ mat_g2 += (np.tensordot(grad_np[i], grad_np[i], axes=([0, 2], [0, 2]))
+ * precond_update_interval)
+ mat_g3 += (np.tensordot(grad_np[i], grad_np[i], axes=([0, 1], [0, 1]))
+ * precond_update_interval)
+
+ if (i + 1) % svd_interval == 0:
+ mat_g1_a = np_power(mat_g1 + 0.1 * np.eye(size[0]), -0.5/3.0)
+ mat_g2_a = np_power(mat_g2 + 0.1 * np.eye(size[1]), -0.5/3.0)
+ mat_g3_a = np_power(mat_g3 + 0.1 * np.eye(size[2]), -0.5/3.0)
+
+ precond_grad = np.tensordot(grad_np[i], mat_g1_a, axes=([0], [0]))
+ precond_grad = np.tensordot(precond_grad, mat_g2_a, axes=([0], [0]))
+ precond_grad = np.tensordot(precond_grad, mat_g3_a, axes=([0], [0]))
+ new_val_np -= precond_grad
+
+ self.assertAllCloseAccordingToType(new_val_np, new_val,
+ atol=TOLERANCE, rtol=TOLERANCE)
+
+ def testDelayedPrecondUpdate(self):
+ for use_iterative_root in [True, False]:
+ self._testDelayedPrecondUpdate(use_iterative_root)
+
+
+if __name__ == '__main__':
+ test.main()
diff --git a/tensorflow/contrib/optimizer_v2/optimizer_v2_test.py b/tensorflow/contrib/optimizer_v2/optimizer_v2_test.py
index ec033c4a01..a44bfd1bfd 100644
--- a/tensorflow/contrib/optimizer_v2/optimizer_v2_test.py
+++ b/tensorflow/contrib/optimizer_v2/optimizer_v2_test.py
@@ -38,12 +38,8 @@ class OptimizerTest(test.TestCase):
@test_util.run_in_graph_and_eager_modes
def testBasic(self):
for i, dtype in enumerate([dtypes.half, dtypes.float32, dtypes.float64]):
- # Note that we name the variables uniquely here since the variables don't
- # seem to be getting deleted at the end of the loop.
- var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtype,
- name='a_%d' % i)
- var1 = resource_variable_ops.ResourceVariable([3.0, 4.0], dtype=dtype,
- name='b_%d' % i)
+ var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtype)
+ var1 = resource_variable_ops.ResourceVariable([3.0, 4.0], dtype=dtype)
def loss():
return 5 * var0 + 3 * var1 # pylint: disable=cell-var-from-loop
# Note that for eager execution, minimize expects a function instead of a
@@ -131,12 +127,8 @@ class OptimizerTest(test.TestCase):
@test_util.run_in_graph_and_eager_modes
def testNoGradients(self):
for i, dtype in enumerate([dtypes.half, dtypes.float32, dtypes.float64]):
- # Note that we name the variables uniquely here since the variables don't
- # seem to be getting deleted at the end of the loop.
- var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtype,
- name='a%d' % i)
- var1 = resource_variable_ops.ResourceVariable([3.0, 4.0], dtype=dtype,
- name='b%d' % i)
+ var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtype)
+ var1 = resource_variable_ops.ResourceVariable([3.0, 4.0], dtype=dtype)
# pylint: disable=cell-var-from-loop
def loss():
return 5 * var0
@@ -149,12 +141,8 @@ class OptimizerTest(test.TestCase):
@test_util.run_in_graph_and_eager_modes
def testNoGradientsForAnyVariables_Minimize(self):
for i, dtype in enumerate([dtypes.half, dtypes.float32, dtypes.float64]):
- # Note that we name the variables uniquely here since the variables don't
- # seem to be getting deleted at the end of the loop.
- var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtype,
- name='a_%d' % i)
- var1 = resource_variable_ops.ResourceVariable([3.0, 4.0], dtype=dtype,
- name='b_%d' % i)
+ var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtype)
+ var1 = resource_variable_ops.ResourceVariable([3.0, 4.0], dtype=dtype)
def loss():
return constant_op.constant(5.0)
sgd_op = gradient_descent.GradientDescentOptimizer(3.0)
@@ -165,12 +153,8 @@ class OptimizerTest(test.TestCase):
@test_util.run_in_graph_and_eager_modes
def testNoGradientsForAnyVariables_ApplyGradients(self):
for i, dtype in enumerate([dtypes.half, dtypes.float32, dtypes.float64]):
- # Note that we name the variables uniquely here since the variables don't
- # seem to be getting deleted at the end of the loop.
- var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtype,
- name='a_%d' % i)
- var1 = resource_variable_ops.ResourceVariable([3.0, 4.0], dtype=dtype,
- name='b_%d' % i)
+ var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtype)
+ var1 = resource_variable_ops.ResourceVariable([3.0, 4.0], dtype=dtype)
sgd_op = gradient_descent.GradientDescentOptimizer(3.0)
with self.assertRaisesRegexp(ValueError,
'No gradients provided for any variable'):
@@ -179,12 +163,8 @@ class OptimizerTest(test.TestCase):
@test_util.run_in_graph_and_eager_modes
def testGradientsAsVariables(self):
for i, dtype in enumerate([dtypes.half, dtypes.float32, dtypes.float64]):
- # Note that we name the variables uniquely here since the variables don't
- # seem to be getting deleted at the end of the loop.
- var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtype,
- name='a%d' % i)
- var1 = resource_variable_ops.ResourceVariable([3.0, 4.0], dtype=dtype,
- name='b%d' % i)
+ var0 = resource_variable_ops.ResourceVariable([1.0, 2.0], dtype=dtype)
+ var1 = resource_variable_ops.ResourceVariable([3.0, 4.0], dtype=dtype)
def loss():
return 5 * var0 + 3 * var1 # pylint: disable=cell-var-from-loop
sgd_op = gradient_descent.GradientDescentOptimizer(3.0)
diff --git a/tensorflow/contrib/recurrent/python/ops/recurrent.py b/tensorflow/contrib/recurrent/python/ops/recurrent.py
index fa16b82ab6..4f289e0c85 100644
--- a/tensorflow/contrib/recurrent/python/ops/recurrent.py
+++ b/tensorflow/contrib/recurrent/python/ops/recurrent.py
@@ -79,7 +79,7 @@ def _Index(struct, index):
"""
index = ops.convert_to_tensor(index)
index.get_shape().assert_has_rank(0)
- return nest.map_structure(lambda x: x[index], struct)
+ return nest.map_structure(lambda x: array_ops.gather(x, index), struct)
def _Update(struct_acc, struct_x, t):
diff --git a/tensorflow/contrib/saved_model/BUILD b/tensorflow/contrib/saved_model/BUILD
index 26fd4e2023..fbb50befdf 100644
--- a/tensorflow/contrib/saved_model/BUILD
+++ b/tensorflow/contrib/saved_model/BUILD
@@ -93,3 +93,32 @@ py_test(
"//tensorflow/python/saved_model:utils",
],
)
+
+py_library(
+ name = "keras_saved_model",
+ srcs = ["python/saved_model/keras_saved_model.py"],
+ srcs_version = "PY2AND3",
+ tags = ["no_windows"],
+ visibility = ["//visibility:public"],
+ deps = [
+ "//tensorflow/python:lib",
+ "//tensorflow/python:util",
+ "//tensorflow/python/keras:engine",
+ "//tensorflow/python/saved_model:constants",
+ ],
+)
+
+py_test(
+ name = "keras_saved_model_test",
+ size = "small",
+ srcs = ["python/saved_model/keras_saved_model_test.py"],
+ srcs_version = "PY2AND3",
+ tags = ["no_windows"],
+ deps = [
+ ":saved_model_py",
+ "//tensorflow/python:client_testlib",
+ "//tensorflow/python:training",
+ "//tensorflow/python/keras",
+ "//third_party/py/numpy",
+ ],
+)
diff --git a/tensorflow/contrib/saved_model/__init__.py b/tensorflow/contrib/saved_model/__init__.py
index b4f27a055d..95e1a8967b 100644
--- a/tensorflow/contrib/saved_model/__init__.py
+++ b/tensorflow/contrib/saved_model/__init__.py
@@ -24,11 +24,12 @@ from __future__ import division
from __future__ import print_function
# pylint: disable=unused-import,wildcard-import,line-too-long
+from tensorflow.contrib.saved_model.python.saved_model.keras_saved_model import *
from tensorflow.contrib.saved_model.python.saved_model.signature_def_utils import *
# pylint: enable=unused-import,widcard-import,line-too-long
from tensorflow.python.util.all_util import remove_undocumented
-_allowed_symbols = ["get_signature_def_by_key"]
+_allowed_symbols = ["get_signature_def_by_key", "load_model", "save_model"]
remove_undocumented(__name__, _allowed_symbols)
diff --git a/tensorflow/contrib/saved_model/python/saved_model/__init__.py b/tensorflow/contrib/saved_model/python/saved_model/__init__.py
index 7b91622b61..e3b76bb6f3 100644
--- a/tensorflow/contrib/saved_model/python/saved_model/__init__.py
+++ b/tensorflow/contrib/saved_model/python/saved_model/__init__.py
@@ -24,5 +24,6 @@ from __future__ import division
from __future__ import print_function
# pylint: disable=wildcard-import
+from tensorflow.contrib.saved_model.python.saved_model import keras_saved_model
from tensorflow.contrib.saved_model.python.saved_model import signature_def_utils
# pylint: enable=wildcard-import
diff --git a/tensorflow/contrib/saved_model/python/saved_model/keras_saved_model.py b/tensorflow/contrib/saved_model/python/saved_model/keras_saved_model.py
new file mode 100644
index 0000000000..e2a969f053
--- /dev/null
+++ b/tensorflow/contrib/saved_model/python/saved_model/keras_saved_model.py
@@ -0,0 +1,108 @@
+# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+# pylint: disable=protected-access
+"""Utility functions to save/load keras Model to/from SavedModel."""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+
+from tensorflow.python.keras.models import model_from_json
+from tensorflow.python.lib.io import file_io
+from tensorflow.python.saved_model import constants
+from tensorflow.python.util import compat
+
+
+def save_model(model, saved_model_path):
+ """Save a `tf.keras.Model` into Tensorflow SavedModel format.
+
+ `save_model` generates such files/folders under the `saved_model_path` folder:
+ 1) an asset folder containing the json string of the model's
+ configuration(topology).
+ 2) a checkpoint containing the model weights.
+
+ Note that subclassed models can not be saved via this function, unless you
+ provide an implementation for get_config() and from_config().
+ Also note that `tf.keras.optimizers.Optimizer` instances can not currently be
+ saved to checkpoints. Use optimizers from `tf.train`.
+
+ Args:
+ model: A `tf.keras.Model` to be saved.
+ saved_model_path: a string specifying the path to the SavedModel directory.
+
+ Raises:
+ NotImplementedError: If the passed in model is a subclassed model.
+ """
+ if not model._is_graph_network:
+ raise NotImplementedError
+
+ # save model configuration as a json string under assets folder.
+ model_json = model.to_json()
+ assets_destination_dir = os.path.join(
+ compat.as_bytes(saved_model_path),
+ compat.as_bytes(constants.ASSETS_DIRECTORY))
+
+ if not file_io.file_exists(assets_destination_dir):
+ file_io.recursive_create_dir(assets_destination_dir)
+
+ model_json_filepath = os.path.join(
+ compat.as_bytes(assets_destination_dir),
+ compat.as_bytes(constants.SAVED_MODEL_FILENAME_JSON))
+ file_io.write_string_to_file(model_json_filepath, model_json)
+
+ # save model weights in checkpoint format.
+ checkpoint_destination_dir = os.path.join(
+ compat.as_bytes(saved_model_path),
+ compat.as_bytes(constants.VARIABLES_DIRECTORY))
+
+ if not file_io.file_exists(checkpoint_destination_dir):
+ file_io.recursive_create_dir(checkpoint_destination_dir)
+
+ checkpoint_prefix = os.path.join(
+ compat.as_text(checkpoint_destination_dir),
+ compat.as_text(constants.VARIABLES_FILENAME))
+ model.save_weights(checkpoint_prefix, save_format='tf', overwrite=True)
+
+
+def load_model(saved_model_path):
+ """Load a keras.Model from SavedModel.
+
+ load_model reinstantiates model state by:
+ 1) loading model topology from json (this will eventually come
+ from metagraph).
+ 2) loading model weights from checkpoint.
+
+ Args:
+ saved_model_path: a string specifying the path to an existing SavedModel.
+
+ Returns:
+ a keras.Model instance.
+ """
+ # restore model topology from json string
+ model_json_filepath = os.path.join(
+ compat.as_bytes(saved_model_path),
+ compat.as_bytes(constants.ASSETS_DIRECTORY),
+ compat.as_bytes(constants.SAVED_MODEL_FILENAME_JSON))
+ model_json = file_io.read_file_to_string(model_json_filepath)
+ model = model_from_json(model_json)
+
+ # restore model weights
+ checkpoint_prefix = os.path.join(
+ compat.as_text(saved_model_path),
+ compat.as_text(constants.VARIABLES_DIRECTORY),
+ compat.as_text(constants.VARIABLES_FILENAME))
+ model.load_weights(checkpoint_prefix)
+ return model
diff --git a/tensorflow/contrib/saved_model/python/saved_model/keras_saved_model_test.py b/tensorflow/contrib/saved_model/python/saved_model/keras_saved_model_test.py
new file mode 100644
index 0000000000..107ae1b07b
--- /dev/null
+++ b/tensorflow/contrib/saved_model/python/saved_model/keras_saved_model_test.py
@@ -0,0 +1,201 @@
+# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+# pylint: disable=protected-access
+"""Tests for saving/loading function for keras Model."""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+import shutil
+import numpy as np
+
+from tensorflow.contrib.saved_model.python.saved_model import keras_saved_model
+from tensorflow.python import keras
+from tensorflow.python.framework import test_util
+from tensorflow.python.keras.engine import training
+from tensorflow.python.platform import test
+from tensorflow.python.training import training as training_module
+
+
+class TestModelSavingandLoading(test.TestCase):
+
+ def test_saving_sequential_model(self):
+ with self.test_session():
+ model = keras.models.Sequential()
+ model.add(keras.layers.Dense(2, input_shape=(3,)))
+ model.add(keras.layers.RepeatVector(3))
+ model.add(keras.layers.TimeDistributed(keras.layers.Dense(3)))
+ model.compile(
+ loss=keras.losses.MSE,
+ optimizer=keras.optimizers.RMSprop(lr=0.0001),
+ metrics=[keras.metrics.categorical_accuracy],
+ sample_weight_mode='temporal')
+ x = np.random.random((1, 3))
+ y = np.random.random((1, 3, 3))
+ model.train_on_batch(x, y)
+
+ ref_y = model.predict(x)
+ temp_dir = self.get_temp_dir()
+ self.addCleanup(shutil.rmtree, temp_dir)
+
+ temp_saved_model = os.path.join(temp_dir, 'saved_model')
+ keras_saved_model.save_model(model, temp_saved_model)
+
+ loaded_model = keras_saved_model.load_model(temp_saved_model)
+ y = loaded_model.predict(x)
+ self.assertAllClose(ref_y, y, atol=1e-05)
+
+ @test_util.run_in_graph_and_eager_modes
+ def test_saving_sequential_model_without_compile(self):
+ with self.test_session():
+ model = keras.models.Sequential()
+ model.add(keras.layers.Dense(2, input_shape=(3,)))
+ model.add(keras.layers.RepeatVector(3))
+ model.add(keras.layers.TimeDistributed(keras.layers.Dense(3)))
+
+ x = np.random.random((1, 3))
+ ref_y = model.predict(x)
+
+ temp_dir = self.get_temp_dir()
+ self.addCleanup(shutil.rmtree, temp_dir)
+
+ temp_saved_model = os.path.join(temp_dir, 'saved_model')
+ keras_saved_model.save_model(model, temp_saved_model)
+ loaded_model = keras_saved_model.load_model(temp_saved_model)
+
+ y = loaded_model.predict(x)
+ self.assertAllClose(ref_y, y, atol=1e-05)
+
+ def test_saving_functional_model(self):
+ with self.test_session():
+ inputs = keras.layers.Input(shape=(3,))
+ x = keras.layers.Dense(2)(inputs)
+ output = keras.layers.Dense(3)(x)
+
+ model = keras.models.Model(inputs, output)
+ model.compile(
+ loss=keras.losses.MSE,
+ optimizer=keras.optimizers.RMSprop(lr=0.0001),
+ metrics=[keras.metrics.categorical_accuracy])
+ x = np.random.random((1, 3))
+ y = np.random.random((1, 3))
+ model.train_on_batch(x, y)
+
+ ref_y = model.predict(x)
+ temp_dir = self.get_temp_dir()
+ self.addCleanup(shutil.rmtree, temp_dir)
+
+ temp_saved_model = os.path.join(temp_dir, 'saved_model')
+ keras_saved_model.save_model(model, temp_saved_model)
+ loaded_model = keras_saved_model.load_model(temp_saved_model)
+
+ y = loaded_model.predict(x)
+ self.assertAllClose(ref_y, y, atol=1e-05)
+
+ @test_util.run_in_graph_and_eager_modes
+ def test_saving_functional_model_without_compile(self):
+ with self.test_session():
+ inputs = keras.layers.Input(shape=(3,))
+ x = keras.layers.Dense(2)(inputs)
+ output = keras.layers.Dense(3)(x)
+
+ model = keras.models.Model(inputs, output)
+
+ x = np.random.random((1, 3))
+ y = np.random.random((1, 3))
+
+ ref_y = model.predict(x)
+ temp_dir = self.get_temp_dir()
+ self.addCleanup(shutil.rmtree, temp_dir)
+
+ temp_saved_model = os.path.join(temp_dir, 'saved_model')
+ keras_saved_model.save_model(model, temp_saved_model)
+ loaded_model = keras_saved_model.load_model(temp_saved_model)
+
+ y = loaded_model.predict(x)
+ self.assertAllClose(ref_y, y, atol=1e-05)
+
+ @test_util.run_in_graph_and_eager_modes
+ def test_saving_with_tf_optimizer(self):
+ with self.test_session():
+ model = keras.models.Sequential()
+ model.add(keras.layers.Dense(2, input_shape=(3,)))
+ model.add(keras.layers.Dense(3))
+ model.compile(
+ loss='mse',
+ optimizer=training_module.RMSPropOptimizer(0.1),
+ metrics=['acc'])
+
+ x = np.random.random((1, 3))
+ y = np.random.random((1, 3))
+ model.train_on_batch(x, y)
+
+ ref_y = model.predict(x)
+ temp_dir = self.get_temp_dir()
+ self.addCleanup(shutil.rmtree, temp_dir)
+
+ temp_saved_model = os.path.join(temp_dir, 'saved_model')
+ keras_saved_model.save_model(model, temp_saved_model)
+ loaded_model = keras_saved_model.load_model(temp_saved_model)
+ loaded_model.compile(
+ loss='mse',
+ optimizer=training_module.RMSPropOptimizer(0.1),
+ metrics=['acc'])
+ y = loaded_model.predict(x)
+ self.assertAllClose(ref_y, y, atol=1e-05)
+
+ # test that new updates are the same with both models
+ x = np.random.random((1, 3))
+ y = np.random.random((1, 3))
+
+ ref_loss = model.train_on_batch(x, y)
+ loss = loaded_model.train_on_batch(x, y)
+ self.assertAllClose(ref_loss, loss, atol=1e-05)
+
+ ref_y = model.predict(x)
+ y = loaded_model.predict(x)
+ self.assertAllClose(ref_y, y, atol=1e-05)
+
+ # test saving/loading again
+ keras_saved_model.save_model(loaded_model, temp_saved_model)
+ loaded_model = keras_saved_model.load_model(temp_saved_model)
+ y = loaded_model.predict(x)
+ self.assertAllClose(ref_y, y, atol=1e-05)
+
+ def test_saving_subclassed_model_raise_error(self):
+ # For now, saving subclassed model should raise an error. It should be
+ # avoided later with loading from SavedModel.pb.
+
+ class SubclassedModel(training.Model):
+
+ def __init__(self):
+ super(SubclassedModel, self).__init__()
+ self.layer1 = keras.layers.Dense(3)
+ self.layer2 = keras.layers.Dense(1)
+
+ def call(self, inp):
+ return self.layer2(self.layer1(inp))
+
+ model = SubclassedModel()
+ temp_dir = self.get_temp_dir()
+ self.addCleanup(shutil.rmtree, temp_dir)
+ temp_saved_model = os.path.join(temp_dir, 'saved_model')
+ with self.assertRaises(NotImplementedError):
+ keras_saved_model.save_model(model, temp_saved_model)
+
+
+if __name__ == '__main__':
+ test.main()
diff --git a/tensorflow/contrib/tensor_forest/kernels/v4/decision_node_evaluator.cc b/tensorflow/contrib/tensor_forest/kernels/v4/decision_node_evaluator.cc
index 7e25579070..6cb2c881e2 100644
--- a/tensorflow/contrib/tensor_forest/kernels/v4/decision_node_evaluator.cc
+++ b/tensorflow/contrib/tensor_forest/kernels/v4/decision_node_evaluator.cc
@@ -51,7 +51,8 @@ std::unique_ptr<DecisionNodeEvaluator> CreateBinaryDecisionNodeEvaluator(
InequalityDecisionNodeEvaluator::InequalityDecisionNodeEvaluator(
const decision_trees::InequalityTest& test, int32 left, int32 right)
: BinaryDecisionNodeEvaluator(left, right) {
- safe_strto32(test.feature_id().id().value(), &feature_num_);
+ CHECK(safe_strto32(test.feature_id().id().value(), &feature_num_))
+ << "Invalid feature ID: [" << test.feature_id().id().value() << "]";
threshold_ = test.threshold().float_value();
include_equals_ =
test.type() == decision_trees::InequalityTest::LESS_OR_EQUAL;
@@ -72,7 +73,9 @@ ObliqueInequalityDecisionNodeEvaluator::ObliqueInequalityDecisionNodeEvaluator(
: BinaryDecisionNodeEvaluator(left, right) {
for (int i = 0; i < test.oblique().features_size(); ++i) {
int32 val;
- safe_strto32(test.oblique().features(i).id().value(), &val);
+ CHECK(safe_strto32(test.oblique().features(i).id().value(), &val))
+ << "Invalid feature ID: [" << test.oblique().features(i).id().value()
+ << "]";
feature_num_.push_back(val);
feature_weights_.push_back(test.oblique().weights(i));
}
@@ -97,7 +100,8 @@ int32 ObliqueInequalityDecisionNodeEvaluator::Decide(
MatchingValuesDecisionNodeEvaluator::MatchingValuesDecisionNodeEvaluator(
const decision_trees::MatchingValuesTest& test, int32 left, int32 right)
: BinaryDecisionNodeEvaluator(left, right) {
- safe_strto32(test.feature_id().id().value(), &feature_num_);
+ CHECK(safe_strto32(test.feature_id().id().value(), &feature_num_))
+ << "Invalid feature ID: [" << test.feature_id().id().value() << "]";
for (const auto& val : test.value()) {
values_.push_back(val.float_value());
}
diff --git a/tensorflow/contrib/tensorrt/BUILD b/tensorflow/contrib/tensorrt/BUILD
index 2fe1f2c242..60e8368c37 100644
--- a/tensorflow/contrib/tensorrt/BUILD
+++ b/tensorflow/contrib/tensorrt/BUILD
@@ -3,7 +3,7 @@
# and provide TensorRT operators and converter package.
# APIs are meant to change over time.
-package(default_visibility = ["//tensorflow:__subpackages__"])
+package(default_visibility = ["//visibility:public"])
licenses(["notice"]) # Apache 2.0
@@ -37,7 +37,9 @@ tf_cuda_cc_test(
"nomac",
],
deps = [
+ "//tensorflow/core:gpu_init",
"//tensorflow/core:lib",
+ "//tensorflow/core:stream_executor",
"//tensorflow/core:test",
"//tensorflow/core:test_main",
] + if_tensorrt([
@@ -120,7 +122,6 @@ tf_cuda_library(
tf_gen_op_wrapper_py(
name = "trt_engine_op",
- gen_locally = True,
deps = [
":trt_engine_op_op_lib",
":trt_logging",
@@ -384,14 +385,15 @@ cuda_py_tests(
"test/base_test.py",
# "test/batch_matmul_test.py",
# "test/biasadd_matmul_test.py",
- "test/binary_tensor_weight_broadcast_test.py",
- "test/concatenation_test.py",
+ # "test/binary_tensor_weight_broadcast_test.py", # Blocked by trt4 installation
+ # "test/concatenation_test.py", # Blocked by trt4 installation
"test/const_broadcast_test.py",
"test/multi_connection_neighbor_engine_test.py",
"test/neighboring_engine_test.py",
- "test/unary_test.py",
+ # "test/unary_test.py", # Blocked by trt4 installation
# "test/vgg_block_nchw_test.py",
# "test/vgg_block_test.py",
+ "test/memory_alignment_test.py",
],
additional_deps = [
":tf_trt_integration_test_base",
diff --git a/tensorflow/contrib/tensorrt/convert/utils.cc b/tensorflow/contrib/tensorrt/convert/utils.cc
index 24591cf84b..17857cf4d0 100644
--- a/tensorflow/contrib/tensorrt/convert/utils.cc
+++ b/tensorflow/contrib/tensorrt/convert/utils.cc
@@ -24,7 +24,7 @@ bool IsGoogleTensorRTEnabled() {
// safely write code that uses tensorrt conditionally. E.g. if it does not
// check for for tensorrt, and user mistakenly uses tensorrt, they will just
// crash and burn.
-#ifdef GOOGLE_TENSORRT
+#if GOOGLE_CUDA && GOOGLE_TENSORRT
return true;
#else
return false;
diff --git a/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc b/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc
index 646d62483f..6699b71d28 100644
--- a/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc
+++ b/tensorflow/contrib/tensorrt/kernels/trt_engine_op.cc
@@ -45,11 +45,11 @@ using ::tensorflow::strings::StrCat;
// Helps simultaneous execution of native and TRT engines.
class AsyncHelper : public tensorflow::core::RefCounted {
public:
- AsyncHelper(tensorflow::AsyncOpKernel::DoneCallback done) { done_ = done; }
+ AsyncHelper(AsyncOpKernel::DoneCallback done) { done_ = done; }
~AsyncHelper() override { done_(); }
private:
- tensorflow::AsyncOpKernel::DoneCallback done_;
+ AsyncOpKernel::DoneCallback done_;
};
#define TYPECASE(dt, X, Y) \
@@ -152,7 +152,7 @@ TRTEngineOp::TRTEngineOp(OpKernelConstruction* context)
}
}
-void TRTEngineOp::ExecuteNativeSegment(tensorflow::OpKernelContext* ctx,
+void TRTEngineOp::ExecuteNativeSegment(OpKernelContext* ctx,
AsyncHelper* helper) {
if (!calibration_mode_) {
VLOG(1) << "Executing native engine";
@@ -193,7 +193,7 @@ void TRTEngineOp::ExecuteNativeSegment(tensorflow::OpKernelContext* ctx,
});
}
-void TRTEngineOp::ExecuteCalibration(tensorflow::OpKernelContext* ctx,
+void TRTEngineOp::ExecuteCalibration(OpKernelContext* ctx,
AsyncHelper* helper) {
helper->Ref();
tensorflow::core::ScopedUnref sc(helper);
@@ -238,7 +238,7 @@ void TRTEngineOp::ExecuteCalibration(tensorflow::OpKernelContext* ctx,
ExecuteNativeSegment(ctx, helper);
}
-int TRTEngineOp::GetEngineBatch(tensorflow::OpKernelContext* ctx) {
+int TRTEngineOp::GetEngineBatch(OpKernelContext* ctx) {
int num_batch = ctx->input(0).shape().dim_size(0);
int smallest_engine = 0;
for (const auto i : cached_engine_batches_) {
@@ -254,21 +254,20 @@ int TRTEngineOp::GetEngineBatch(tensorflow::OpKernelContext* ctx) {
cached_engine_batches_.push_back(num_batch);
VLOG(1) << "Running with batch size " << num_batch;
} else {
- string s("Engine buffer is full. buffer limit= ");
- StrAppend(&s, max_cached_engines_, ", current entries= ");
- for (auto i : cached_engine_batches_) StrAppend(&s, i, ", ");
- StrAppend(&s, "Requested batch= ", num_batch);
- LOG(ERROR) << s;
- ctx->SetStatus(tensorflow::errors::ResourceExhausted(
- "Requested batch size is not available and engine cache is full"));
+ string msg =
+ StrCat("Engine buffer is full. buffer limit=", max_cached_engines_,
+ ", current entries=");
+ for (auto i : cached_engine_batches_) StrAppend(&msg, i, ",");
+ StrAppend(&msg, "Requested batch=", num_batch);
+ LOG(WARNING) << msg;
return -1;
}
}
return smallest_engine;
}
-void TRTEngineOp::ComputeAsync(tensorflow::OpKernelContext* ctx,
- tensorflow::AsyncOpKernel::DoneCallback done) {
+void TRTEngineOp::ComputeAsync(OpKernelContext* ctx,
+ AsyncOpKernel::DoneCallback done) {
auto helper = new AsyncHelper(done);
tensorflow::core::ScopedUnref sc(helper);
if (calibration_mode_) {
@@ -276,32 +275,52 @@ void TRTEngineOp::ComputeAsync(tensorflow::OpKernelContext* ctx,
return;
}
const int smallest_engine = GetEngineBatch(ctx);
- if (smallest_engine < 0) return; // GetEngineBatch already set the status.
+ if (smallest_engine < 0) {
+ LOG(WARNING) << "Failed to get engine batch, running native segment";
+ ExecuteNativeSegment(ctx, helper);
+ return;
+ }
const int num_batch = ctx->input(0).shape().dim_size(0);
auto& engine_ctx_pair = GetEngine(smallest_engine, ctx);
auto& trt_engine_ptr = engine_ctx_pair.first;
if (!trt_engine_ptr) {
LOG(WARNING) << "Engine retrieval for batch size " << num_batch
- << " failed Running native segment";
+ << " failed. Running native segment";
+ ExecuteNativeSegment(ctx, helper);
+ return;
+ }
+ const bool retry = ExecuteTrtEngine(ctx, num_batch, trt_engine_ptr.get(),
+ engine_ctx_pair.second.get());
+ if (retry) {
+ LOG(WARNING) << "Failed to execute engine, retrying with native segment";
ExecuteNativeSegment(ctx, helper);
return;
}
+}
+bool TRTEngineOp::ExecuteTrtEngine(
+ OpKernelContext* ctx, const int num_batch,
+ nvinfer1::ICudaEngine* trt_engine_ptr,
+ nvinfer1::IExecutionContext* trt_execution_context_ptr) {
+ const bool kRetry = true;
const int num_binding = ctx->num_inputs() + ctx->num_outputs();
std::vector<void*> buffers(num_binding);
for (int i = 0; i < ctx->num_inputs(); i++) {
- const string inp_name = StrCat(kInputPHName, i);
+ const string input_name = StrCat(kInputPHName, i);
const size_t binding_index =
- trt_engine_ptr->getBindingIndex(inp_name.c_str());
+ trt_engine_ptr->getBindingIndex(input_name.c_str());
+ if (binding_index == -1) {
+ LOG(ERROR) << "Input node not found, at " << input_name;
+ return kRetry;
+ }
const Tensor& input_tensor = ctx->input(i);
const TensorShape& input_shape = input_tensor.shape();
if (num_batch != input_shape.dim_size(0)) {
- LOG(ERROR) << "input data inconsistent batch size";
- ctx->SetStatus(tensorflow::errors::FailedPrecondition(
- "Different batch sizes between input tensors"));
- return;
+ LOG(ERROR) << "Input data has inconsistent batch size: " << num_batch
+ << " vs " << input_shape.dim_size(0);
+ return kRetry;
}
auto dtype = trt_engine_ptr->getBindingDataType(binding_index);
switch (dtype) {
@@ -310,14 +329,10 @@ void TRTEngineOp::ComputeAsync(tensorflow::OpKernelContext* ctx,
break;
case nvinfer1::DataType::kHALF:
LOG(ERROR) << "FP16 inputs are not supported yet!";
- ctx->SetStatus(tensorflow::errors::InvalidArgument(
- "FP16 inputs are not supported!"));
- return;
+ return kRetry;
case nvinfer1::DataType::kINT8:
LOG(ERROR) << "INT8 inputs are not supported yet!";
- ctx->SetStatus(tensorflow::errors::InvalidArgument(
- "INT8 inputs are not supported!"));
- return;
+ return kRetry;
#if NV_TENSORRT_MAJOR > 3
case nvinfer1::DataType::kINT32:
buffers[binding_index] = (void*)(input_tensor.flat<int32>().data());
@@ -325,9 +340,7 @@ void TRTEngineOp::ComputeAsync(tensorflow::OpKernelContext* ctx,
#endif
default:
LOG(ERROR) << "Unknown TRT data type: " << int(dtype);
- ctx->SetStatus(tensorflow::errors::InvalidArgument(
- "Unknown output TRT data type! ", static_cast<int>(dtype)));
- return;
+ return kRetry;
}
}
@@ -344,20 +357,23 @@ void TRTEngineOp::ComputeAsync(tensorflow::OpKernelContext* ctx,
std::vector<int> trt_shape(dims.nbDims + 1);
trt_shape[0] = num_batch;
for (int j = 0; j < dims.nbDims; j++) trt_shape[j + 1] = dims.d[j];
- OP_REQUIRES_OK(
- ctx, TensorShapeUtils::MakeShape(trt_shape.data(), trt_shape.size(),
- &output_shape));
+ auto status = TensorShapeUtils::MakeShape(
+ trt_shape.data(), trt_shape.size(), &output_shape);
+ if (!status.ok()) {
+ LOG(ERROR) << "Failed to get output shape: " << status;
+ return kRetry;
+ }
} else {
- LOG(ERROR) << "output node not found, at " << output_name;
- ctx->SetStatus(tensorflow::errors::Internal("output ", output_name,
- " couldn't be found!"));
- return;
+ LOG(ERROR) << "Output node not found, at " << output_name;
+ return kRetry;
}
auto status = ctx->allocate_output(i, output_shape, &output_tensor);
if (!status.ok()) {
LOG(ERROR) << "Allocating output failed with " << status;
ctx->SetStatus(status);
- return;
+ // Do not retry since we cannot allocate the same output twice.
+ // TODO(aaroey): ideally we should retry, fix this.
+ return !kRetry;
}
auto dtype = trt_engine_ptr->getBindingDataType(binding_index);
switch (dtype) {
@@ -366,15 +382,11 @@ void TRTEngineOp::ComputeAsync(tensorflow::OpKernelContext* ctx,
reinterpret_cast<void*>(output_tensor->flat<float>().data());
break;
case nvinfer1::DataType::kHALF:
- LOG(ERROR) << "half size is not supported yet!";
- ctx->SetStatus(tensorflow::errors::InvalidArgument(
- "Half outputs are not supported!"));
- return;
+ LOG(WARNING) << "half size is not supported yet!";
+ return kRetry;
case nvinfer1::DataType::kINT8:
- LOG(ERROR) << "int8 is not supported yet!";
- ctx->SetStatus(tensorflow::errors::InvalidArgument(
- "INT8 outputs are not supported!"));
- return;
+ LOG(WARNING) << "int8 is not supported yet!";
+ return kRetry;
#if NV_TENSORRT_MAJOR > 3
case nvinfer1::DataType::kINT32:
buffers[binding_index] =
@@ -382,13 +394,11 @@ void TRTEngineOp::ComputeAsync(tensorflow::OpKernelContext* ctx,
break;
#endif
default:
- LOG(ERROR) << "Unknown TRT data type: " << static_cast<int>(dtype);
- ctx->SetStatus(tensorflow::errors::InvalidArgument(
- "Unsupported output data type! ", static_cast<int>(dtype)));
- return;
+ LOG(WARNING) << "Unknown TRT data type: " << static_cast<int>(dtype);
+ return kRetry;
}
}
- // copied from cuda_kernel_helper since it seems only valid in *.cu.cc files
+ // Copied from cuda_kernel_helper since it seems only valid in *.cu.cc files
const cudaStream_t* stream = CHECK_NOTNULL(
reinterpret_cast<const cudaStream_t*>(ctx->op_device_context()
->stream()
@@ -396,15 +406,14 @@ void TRTEngineOp::ComputeAsync(tensorflow::OpKernelContext* ctx,
->GpuStreamMemberHack()));
// TODO(jie): trt enqueue does not return error
- auto& trt_execution_context_ptr = engine_ctx_pair.second;
auto ret = trt_execution_context_ptr->enqueue(num_batch, &buffers[0], *stream,
nullptr);
if (!ret) {
- LOG(ERROR) << "Failed to enqueue batch for TRT engine: " << name();
- ctx->SetStatus(tensorflow::errors::Internal(
- "Failed to enqueue batch for TRT engine: ", name()));
+ LOG(WARNING) << "Failed to enqueue batch for TRT engine: " << name();
+ return kRetry;
}
- // sync should be done by TF.
+ // Synchronization will be done by TF.
+ return !kRetry;
}
TRTEngineOp::~TRTEngineOp() {
@@ -424,8 +433,6 @@ nvinfer1::IGpuAllocator* TRTEngineOp::GetAllocator(OpKernelContext* ctx) {
if (!alloc) {
LOG(ERROR) << "Can't find device allocator for gpu device "
<< device->name();
- ctx->SetStatus(tensorflow::errors::Internal(
- "Can't get device allocator for device ", device->name()));
return nullptr;
}
allocator_.reset(new TRTDeviceAllocator(alloc));
@@ -452,7 +459,6 @@ TRTEngineOp::EngineCtxPair& TRTEngineOp::GetEngine(int batch_size,
#if NV_TENSORRT_MAJOR > 3
auto allocator = GetAllocator(ctx);
if (allocator == nullptr) {
- // GetAllocator already set the Status.
return null_pair;
}
infer->setGpuAllocator(allocator);
@@ -469,7 +475,9 @@ TRTEngineOp::EngineCtxPair& TRTEngineOp::GetEngine(int batch_size,
raw_static_engine->createExecutionContext())};
// Runtime is safe to delete after engine creation
serialized_segment_.clear();
- if (max_batch_size < batch_size) return null_pair;
+ if (max_batch_size < batch_size) {
+ return null_pair;
+ }
return engine_map_.at(max_batch_size);
} // static_engine_
@@ -481,7 +489,6 @@ TRTEngineOp::EngineCtxPair& TRTEngineOp::GetEngine(int batch_size,
#if NV_TENSORRT_MAJOR > 3
allocator = GetAllocator(ctx);
if (allocator == nullptr) {
- // GetAllocator already set the Status.
return null_pair;
}
#endif
@@ -505,9 +512,8 @@ TRTEngineOp::EngineCtxPair& TRTEngineOp::GetEngine(int batch_size,
// retry in the future.
engine_map_[batch_size] = {nullptr, nullptr};
}
- LOG(ERROR) << "Engine creation for batch size " << batch_size
- << " failed " << status;
- ctx->SetStatus(tensorflow::errors::Internal("Engine creation failed!"));
+ LOG(WARNING) << "Engine creation for batch size " << batch_size
+ << " failed " << status;
return null_pair;
}
VLOG(1) << "Conversion is done";
@@ -519,7 +525,7 @@ TRTEngineOp::EngineCtxPair& TRTEngineOp::GetEngine(int batch_size,
}
tensorflow::Status TRTEngineOp::AllocateCalibrationResources(
- tensorflow::OpKernelContext* ctx, TRTCalibrationResource** cr) {
+ OpKernelContext* ctx, TRTCalibrationResource** cr) {
auto cres = new TRTCalibrationResource();
*cr = cres;
// Get the allocator.
diff --git a/tensorflow/contrib/tensorrt/kernels/trt_engine_op.h b/tensorflow/contrib/tensorrt/kernels/trt_engine_op.h
index 9265250605..59b744e6d3 100644
--- a/tensorflow/contrib/tensorrt/kernels/trt_engine_op.h
+++ b/tensorflow/contrib/tensorrt/kernels/trt_engine_op.h
@@ -60,6 +60,12 @@ class TRTEngineOp : public AsyncOpKernel {
// Execute replaced native segment as function Op.
void ExecuteNativeSegment(OpKernelContext* ctx, AsyncHelper* helper);
+ // Execute the tensorrt engine. Returns whether we need to retry by running
+ // the native segment.
+ bool ExecuteTrtEngine(OpKernelContext* ctx, const int num_batch,
+ nvinfer1::ICudaEngine* trt_engine_ptr,
+ nvinfer1::IExecutionContext* trt_execution_context_ptr);
+
// Allocate necessary resources for calibration
Status AllocateCalibrationResources(OpKernelContext* ctx,
TRTCalibrationResource** cr);
diff --git a/tensorflow/contrib/tensorrt/tensorrt_test.cc b/tensorflow/contrib/tensorrt/tensorrt_test.cc
index 3712a9a6fe..769982c645 100644
--- a/tensorflow/contrib/tensorrt/tensorrt_test.cc
+++ b/tensorflow/contrib/tensorrt/tensorrt_test.cc
@@ -13,7 +13,9 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
+#include "tensorflow/core/common_runtime/gpu/gpu_init.h"
#include "tensorflow/core/platform/logging.h"
+#include "tensorflow/core/platform/stream_executor.h"
#include "tensorflow/core/platform/test.h"
#if GOOGLE_CUDA
@@ -130,6 +132,13 @@ void Execute(nvinfer1::IExecutionContext* context, const float* input,
}
TEST(TensorrtTest, BasicFunctions) {
+ // Handle the case where the test is run on machine with no gpu available.
+ if (CHECK_NOTNULL(GPUMachineManager())->VisibleDeviceCount() <= 0) {
+ LOG(WARNING) << "No gpu device available, probably not being run on a gpu "
+ "machine. Skipping...";
+ return;
+ }
+
// Create the network model.
nvinfer1::IHostMemory* model = CreateNetwork();
// Use the model to create an engine and then an execution context.
diff --git a/tensorflow/contrib/tensorrt/test/memory_alignment_test.py b/tensorflow/contrib/tensorrt/test/memory_alignment_test.py
new file mode 100644
index 0000000000..3dd95c6f62
--- /dev/null
+++ b/tensorflow/contrib/tensorrt/test/memory_alignment_test.py
@@ -0,0 +1,72 @@
+# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Model script to test TF-TensorRT integration."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import numpy as np
+
+from tensorflow.contrib.tensorrt.test import tf_trt_integration_test_base as trt_test
+from tensorflow.python.framework import constant_op
+from tensorflow.python.framework import dtypes
+from tensorflow.python.framework import ops
+from tensorflow.python.ops import array_ops
+from tensorflow.python.ops import nn
+from tensorflow.python.platform import test
+
+
+class MemoryAlignmentTest(trt_test.TfTrtIntegrationTestBase):
+
+ def GetParams(self):
+ """Testing conversion of BatchMatMul in TF-TRT conversion."""
+ dtype = dtypes.float32
+ input_name = "input"
+ input_dims = [2, 15, 15, 3]
+ g = ops.Graph()
+ with g.as_default():
+ inp = array_ops.placeholder(
+ dtype=dtype, shape=[None] + input_dims[1:], name=input_name)
+ with g.device("/GPU:0"):
+ e1 = constant_op.constant(
+ np.random.randn(1, 1, 3, 5), name="kernel_1", dtype=dtype)
+ e2 = constant_op.constant(
+ np.random.randn(1, 1, 5, 10), name="kernel_2", dtype=dtype)
+ conv = nn.conv2d(
+ input=inp,
+ filter=e1,
+ strides=[1, 1, 1, 1],
+ padding="VALID",
+ name="conv")
+ out = nn.conv2d(
+ input=conv,
+ filter=e2,
+ strides=[1, 1, 1, 1],
+ padding="VALID",
+ name="conv_2")
+ array_ops.squeeze(out, name=self.output_name)
+ return trt_test.TfTrtIntegrationTestParams(
+ gdef=g.as_graph_def(),
+ input_names=[input_name],
+ input_dims=[input_dims],
+ num_expected_engines=1,
+ expected_output_dims=(2, 15, 15, 10),
+ allclose_atol=1.e-02,
+ allclose_rtol=1.e-02)
+
+
+if __name__ == "__main__":
+ test.main()
diff --git a/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py b/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py
index 60b8eb6e81..bb7f5a77f0 100644
--- a/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py
+++ b/tensorflow/contrib/tensorrt/test/tf_trt_integration_test_base.py
@@ -107,6 +107,7 @@ class TfTrtIntegrationTestBase(test_util.TensorFlowTestCase):
graph_options = config_pb2.GraphOptions()
gpu_options = config_pb2.GPUOptions()
+ gpu_options.allow_growth = True
if trt_convert.get_linked_tensorrt_version()[0] == 3:
gpu_options.per_process_gpu_memory_fraction = 0.50
diff --git a/tensorflow/contrib/timeseries/examples/multivariate.py b/tensorflow/contrib/timeseries/examples/multivariate.py
index ed799542fd..e81cb18ad7 100644
--- a/tensorflow/contrib/timeseries/examples/multivariate.py
+++ b/tensorflow/contrib/timeseries/examples/multivariate.py
@@ -80,8 +80,8 @@ def multivariate_train_and_sample(
session=session, steps=1))
next_sample = numpy.random.multivariate_normal(
# Squeeze out the batch and series length dimensions (both 1).
- mean=numpy.squeeze(current_prediction["mean"], axis=[0, 1]),
- cov=numpy.squeeze(current_prediction["covariance"], axis=[0, 1]))
+ mean=numpy.squeeze(current_prediction["mean"], axis=(0, 1)),
+ cov=numpy.squeeze(current_prediction["covariance"], axis=(0, 1)))
# Update model state so that future predictions are conditional on the
# value we just sampled.
filtering_features = {
diff --git a/tensorflow/contrib/tpu/BUILD b/tensorflow/contrib/tpu/BUILD
index 643a7cc13a..c5855106de 100644
--- a/tensorflow/contrib/tpu/BUILD
+++ b/tensorflow/contrib/tpu/BUILD
@@ -15,6 +15,7 @@ package(
default_visibility = [
"//cloud/vmm/testing/tests/tpu:__subpackages__",
"//learning/brain:__subpackages__",
+ "//learning/deepmind:__subpackages__",
"//tensorflow:__subpackages__",
],
)
@@ -47,6 +48,8 @@ py_library(
deps = [
":tpu_lib",
":tpu_py",
+ "//tensorflow/compiler/xla/experimental/xla_sharding",
+ "//tensorflow/compiler/xla/python_api:xla_shape",
"//tensorflow/contrib/training:training_py",
"//tensorflow/core:protos_all_py",
"//tensorflow/python:array_ops",
diff --git a/tensorflow/contrib/tpu/__init__.py b/tensorflow/contrib/tpu/__init__.py
index d5484e9032..cac346ae30 100644
--- a/tensorflow/contrib/tpu/__init__.py
+++ b/tensorflow/contrib/tpu/__init__.py
@@ -62,7 +62,7 @@ from tensorflow.contrib.tpu.python.tpu.topology import *
from tensorflow.contrib.tpu.python.tpu.tpu import *
from tensorflow.contrib.tpu.python.tpu.tpu_config import *
from tensorflow.contrib.tpu.python.tpu.tpu_estimator import *
-from tensorflow.contrib.tpu.python.tpu.tpu_feed import *
+from tensorflow.contrib.tpu.python.tpu.tpu_feed import InfeedQueue
from tensorflow.contrib.tpu.python.tpu.tpu_optimizer import *
from tensorflow.contrib.tpu.python.tpu.training_loop import *
# pylint: enable=wildcard-import,unused-import
diff --git a/tensorflow/contrib/tpu/profiler/tpu_profiler.proto b/tensorflow/contrib/tpu/profiler/tpu_profiler.proto
index f0fca63db0..da4a95e045 100644
--- a/tensorflow/contrib/tpu/profiler/tpu_profiler.proto
+++ b/tensorflow/contrib/tpu/profiler/tpu_profiler.proto
@@ -11,6 +11,9 @@ service TPUProfiler {
// Starts a profiling session, blocks until it completes, and returns data.
rpc Profile(ProfileRequest) returns (ProfileResponse) {
}
+ // Collects profiling data and returns user-friendly metrics.
+ rpc Monitor(MonitorRequest) returns (MonitorResponse) {
+ }
}
message ProfileOptions {
@@ -104,3 +107,26 @@ message ProfileResponse {
// next-field: 8
}
+
+message MonitorRequest {
+ // Duration for which to profile between each update.
+ uint64 duration_ms = 1;
+
+ // Indicates the level at which we want to monitor. Currently, two levels are
+ // supported:
+ // Level 1: An ultra lightweight mode that captures only some utilization
+ // metrics.
+ // Level 2: More verbose than level 1. Collects utilization metrics, device
+ // information, step time information, etc. Do not use this option if the TPU
+ // host is being very heavily used.
+ int32 monitoring_level = 2;
+
+ // next-field: 3
+}
+
+message MonitorResponse {
+ // Properly formatted string data that can be directly returned back to user.
+ string data = 1;
+
+ // next-field: 2
+}
diff --git a/tensorflow/contrib/tpu/proto/optimization_parameters.proto b/tensorflow/contrib/tpu/proto/optimization_parameters.proto
index 9150606f5e..2cc17d6d92 100644
--- a/tensorflow/contrib/tpu/proto/optimization_parameters.proto
+++ b/tensorflow/contrib/tpu/proto/optimization_parameters.proto
@@ -1,10 +1,12 @@
-syntax = "proto2";
+syntax = "proto3";
package tensorflow.tpu;
+import "google/protobuf/wrappers.proto";
+
message ClippingLimits {
- optional float lower = 1 [default = -inf];
- optional float upper = 2 [default = inf];
+ google.protobuf.FloatValue lower = 1; // -inf if not set
+ google.protobuf.FloatValue upper = 2; // +inf if not set
}
// Get the learning rate from a <yet to be determined> source that can change
@@ -21,18 +23,18 @@ message LearningRate {
}
message AdagradParameters {
- optional float initial_accumulator = 1 [default = 0.];
+ float initial_accumulator = 1;
}
message StochasticGradientDescentParameters {
}
message FtrlParameters {
- optional float l1 = 1 [default = 0.];
- optional float l2 = 2 [default = 0.];
- optional float lr_power = 3 [default = 0.];
- optional float initial_accum = 4 [default = 0.];
- optional float initial_linear = 5 [default = 0.];
+ float l1 = 1;
+ float l2 = 2;
+ float lr_power = 3;
+ float initial_accum = 4;
+ float initial_linear = 5;
}
// The Adam optimizer does not implement hyper-parameter update; use the dynamic
@@ -41,84 +43,84 @@ message FtrlParameters {
// Here, t is the current timestep.
// https://github.com/tensorflow/tensorflow/blob/ab51450c817674c8ff08a7ae4f8ac50cdc4bed8b/tensorflow/python/training/adam.py#L54
message AdamParameters {
- optional float beta1 = 3 [default = 0.];
- optional float beta2 = 4 [default = 0.];
- optional float epsilon = 5 [default = 0.];
- optional float initial_m = 6 [default = 0.];
- optional float initial_v = 7 [default = 0.];
+ float beta1 = 3;
+ float beta2 = 4;
+ float epsilon = 5;
+ float initial_m = 6;
+ float initial_v = 7;
}
message MomentumParameters {
- optional float momentum = 1 [default = 0.];
- optional bool use_nesterov = 2 [default = false];
- optional float initial_accum = 3 [default = 0.];
+ float momentum = 1;
+ bool use_nesterov = 2;
+ float initial_accum = 3;
}
message RmsPropParameters {
- optional float rho = 1 [default = 0.];
- optional float momentum = 2 [default = 0.];
- optional float epsilon = 3 [default = 0.];
- optional float initial_ms = 4 [default = 0.];
- optional float initial_mom = 5 [default = 0.];
+ float rho = 1;
+ float momentum = 2;
+ float epsilon = 3;
+ float initial_ms = 4;
+ float initial_mom = 5;
}
message CenteredRmsPropParameters {
- optional float rho = 1 [default = 0.];
- optional float momentum = 2 [default = 0.];
- optional float epsilon = 3 [default = 0.];
- optional float initial_ms = 4 [default = 0.];
- optional float initial_mom = 5 [default = 0.];
- optional float initial_mg = 6 [default = 0.];
+ float rho = 1;
+ float momentum = 2;
+ float epsilon = 3;
+ float initial_ms = 4;
+ float initial_mom = 5;
+ float initial_mg = 6;
}
message MdlAdagradLightParameters {
- optional float l2 = 1;
- optional float lr_power = 2;
- optional float min_servable_mdl_benefit = 3;
- optional float mdl_mix_in_margin = 4;
- optional float mdl_benefit_rampup_coeff = 5;
- optional float mdl_min_weight = 6;
- optional float benefit_revisit_scale = 7;
- optional float max_event_benefit = 8;
- optional float max_total_benefit = 9;
- optional float mdl_hard_limit = 10;
- optional bool hard_limit_min_benefit = 11;
- optional bool mdl_regularize = 12;
- optional float initial_accumulator = 13;
- optional float initial_weight = 14;
- optional float initial_benefit = 15;
+ float l2 = 1;
+ float lr_power = 2;
+ float min_servable_mdl_benefit = 3;
+ float mdl_mix_in_margin = 4;
+ float mdl_benefit_rampup_coeff = 5;
+ float mdl_min_weight = 6;
+ float benefit_revisit_scale = 7;
+ float max_event_benefit = 8;
+ float max_total_benefit = 9;
+ float mdl_hard_limit = 10;
+ bool hard_limit_min_benefit = 11;
+ bool mdl_regularize = 12;
+ float initial_accumulator = 13;
+ float initial_weight = 14;
+ float initial_benefit = 15;
}
message AdadeltaParameters {
- optional float rho = 1;
- optional float epsilon = 2;
- optional float initial_accumulator = 3 [default = 0.];
- optional float initial_update = 4 [default = 0.];
+ float rho = 1;
+ float epsilon = 2;
+ float initial_accumulator = 3;
+ float initial_update = 4;
}
message ProximalAdagradParameters {
- optional float l1 = 1;
- optional float l2 = 2;
- optional float initial_accumulator = 3;
+ float l1 = 1;
+ float l2 = 2;
+ float initial_accumulator = 3;
}
message OptimizationParameters {
// Learning rate used for updating the embedding layer parameters.
- optional LearningRate learning_rate = 13;
+ LearningRate learning_rate = 13;
reserved 1; // Old learning rate tag.
// Limits to which to clip the weight values after the backward pass; not
// present means no limits are applied.
- optional ClippingLimits clipping_limits = 2;
+ ClippingLimits clipping_limits = 2;
// Limits to which to clip the backward pass gradient before using it for
// updates; not present means no limits are applied.
- optional ClippingLimits gradient_clipping_limits = 7;
+ ClippingLimits gradient_clipping_limits = 7;
// Whether to use gradient accumulation (do two passes over the input
// gradients: one to accumulate them into a temporary array and another to
// apply them using the actual optimization algorithm).
- optional bool use_gradient_accumulation = 15 [default = false];
+ bool use_gradient_accumulation = 15;
// Optimization algorithm parameters; which field is selected determines which
// algorithm to use.
@@ -140,7 +142,7 @@ message OptimizationParameters {
// value vector and any extra accumulators, etc.).
message StateVariableSpecification {
// Parameter name for the state variable.
- optional string name = 1;
+ string name = 1;
// A normal state variable that should be saved and restored in checkpoints
// and used as an input or output to non-debug TensorFlow ops.
@@ -151,7 +153,7 @@ message StateVariableSpecification {
// from users (used for intermediate gradients being accumulated, for
// example).
message FillWithConstant {
- optional double initial_value = 1;
+ double initial_value = 1;
}
// Usage type of this state variable.
diff --git a/tensorflow/contrib/tpu/python/tpu/device_assignment.py b/tensorflow/contrib/tpu/python/tpu/device_assignment.py
index 726b2d248e..471b1fa46c 100644
--- a/tensorflow/contrib/tpu/python/tpu/device_assignment.py
+++ b/tensorflow/contrib/tpu/python/tpu/device_assignment.py
@@ -175,6 +175,8 @@ class DeviceAssignment(object):
"""Returns the physical topology coordinates of a logical core."""
if logical_core is None:
logical_core = np.array([0, 0, 0], np.int32)
+ else:
+ logical_core = np.asarray(logical_core)
if any(logical_core < 0) or any(logical_core >= self.computation_shape):
raise ValueError("Invalid core {}; computation shape is {}".format(
diff --git a/tensorflow/contrib/tpu/python/tpu/error_handling.py b/tensorflow/contrib/tpu/python/tpu/error_handling.py
index 14659fe68f..52e1ea4237 100644
--- a/tensorflow/contrib/tpu/python/tpu/error_handling.py
+++ b/tensorflow/contrib/tpu/python/tpu/error_handling.py
@@ -19,10 +19,11 @@ from __future__ import division
from __future__ import print_function
import contextlib
+import sys
import threading
import time
-import traceback
+import six
from tensorflow.python.framework import errors
from tensorflow.python.platform import tf_logging as logging
@@ -51,7 +52,7 @@ class ErrorRendezvous(object):
self._num_sources = num_sources
self._session_cancel_timer = None
- def record_error(self, source, exception, session=None):
+ def record_error(self, source, exc_info, session=None):
"""Report an exception from the given source.
If a session is passed, a timer will be registered to close it after a few
@@ -61,12 +62,12 @@ class ErrorRendezvous(object):
Args:
source: string, source of the error
- exception: Exception being thrown
+ exc_info: Output from `sys.exc_info` (type, value, traceback)
session: Session to close after delay.
"""
- logging.info('Error recorded from %s: %s', source, exception)
- stack_trace = traceback.format_exc()
- self._errors[source] = (exception, stack_trace)
+ _, value, _ = exc_info
+ self._errors[source] = exc_info
+ logging.info('Error recorded from %s: %s', source, value)
if session is not None and self._session_cancel_timer is None:
@@ -98,8 +99,8 @@ class ErrorRendezvous(object):
"""Context manager to report any errors within a block."""
try:
yield
- except Exception as e: # pylint: disable=broad-except
- self.record_error(source, e, session)
+ except Exception: # pylint: disable=broad-except
+ self.record_error(source, sys.exc_info(), session)
def raise_errors(self, timeout_sec=0):
"""Wait for up to `timeout` seconds for all error sources to finish.
@@ -117,16 +118,15 @@ class ErrorRendezvous(object):
kept_errors = [(k, v) for (k, v) in self._errors.items() if v is not None]
- if not kept_errors:
- return
-
# First check for any interesting errors, then fall back on the session
# cancelled errors etc.
- for k, (exc, _) in kept_errors:
- if isinstance(exc, _UNINTERESTING_ERRORS):
+ for k, (typ, value, traceback) in kept_errors:
+ if isinstance(value, _UNINTERESTING_ERRORS):
continue
else:
- raise exc
+ logging.warn('Reraising captured error')
+ six.reraise(typ, value, traceback)
- for k, (exc, _) in kept_errors:
- raise exc
+ for k, (typ, value, traceback) in kept_errors:
+ logging.warn('Reraising captured error')
+ six.reraise(typ, value, traceback)
diff --git a/tensorflow/contrib/tpu/python/tpu/tpu.py b/tensorflow/contrib/tpu/python/tpu/tpu.py
index 06885bbc25..92c1eaba71 100644
--- a/tensorflow/contrib/tpu/python/tpu/tpu.py
+++ b/tensorflow/contrib/tpu/python/tpu/tpu.py
@@ -314,7 +314,9 @@ class TPUReplicateContext(control_flow_ops.XLAControlFlowContext):
# Capture the device function stack at the time of first entry
# since that is the stack that will be used outside_compilation.
graph = ops.get_default_graph()
- self._outer_device_function_stack = list(graph._device_function_stack) # pylint: disable=protected-access
+ # pylint: disable=protected-access
+ self._outer_device_function_stack = graph._device_function_stack.copy()
+ # pylint: enable=protected-access
super(TPUReplicateContext, self).Enter()
def HostComputeCore(self):
diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_config.py b/tensorflow/contrib/tpu/python/tpu/tpu_config.py
index 9e010922dc..8d05e081a7 100644
--- a/tensorflow/contrib/tpu/python/tpu/tpu_config.py
+++ b/tensorflow/contrib/tpu/python/tpu/tpu_config.py
@@ -44,7 +44,6 @@ class InputPipelineConfig(object):
BROADCAST = 4
-# TODO(b/72511246) Provide a simplified api to configure model parallelism.
class TPUConfig(
collections.namedtuple('TPUConfig', [
'iterations_per_loop',
@@ -53,6 +52,7 @@ class TPUConfig(
'per_host_input_for_training',
'tpu_job_name',
'initial_infeed_sleep_secs',
+ 'input_partition_dims',
])):
r"""TPU related configuration required by `TPUEstimator`.
@@ -90,6 +90,17 @@ class TPUConfig(
initial_infeed_sleep_secs: The number of seconds the infeed thread should
wait before enqueueing the first batch. This helps avoid timeouts for
models that require a long compilation time.
+ input_partition_dims: A nested list to describe the partition dims
+ for all the tensors from input_fn(). The structure of
+ input_partition_dims must match the structure of `features` and
+ `labels` from input_fn(). The total number of partitions must match
+ `num_cores_per_replica`. For example, if input_fn() returns two tensors:
+ images with shape [N, H, W, C] and labels [N].
+ input_partition_dims = [[1, 2, 2, 1], None] will split the images to 4
+ pieces and feed into 4 TPU cores. labels tensor are directly broadcasted
+ to all the TPU cores since the partition dims is `None`.
+ Current limitations: This feature is only supported with the PER_HOST_V2
+ input mode.
Raises:
ValueError: If `computation_shape` or `computation_shape` are invalid.
@@ -101,7 +112,8 @@ class TPUConfig(
num_cores_per_replica=None,
per_host_input_for_training=True,
tpu_job_name=None,
- initial_infeed_sleep_secs=None):
+ initial_infeed_sleep_secs=None,
+ input_partition_dims=None):
# Check iterations_per_loop.
util_lib.check_positive_integer(iterations_per_loop,
@@ -111,6 +123,20 @@ class TPUConfig(
if num_shards is not None:
util_lib.check_positive_integer(num_shards, 'TPUConfig num_shards')
+ if input_partition_dims is not None:
+ if len(input_partition_dims) != 1 and len(input_partition_dims) != 2:
+ raise ValueError(
+ 'input_partition_dims must be a list/tuple with one or two'
+ ' elements.')
+
+ if per_host_input_for_training is not InputPipelineConfig.PER_HOST_V2:
+ raise ValueError(
+ 'input_partition_dims is only supported in PER_HOST_V2 mode.')
+
+ if num_cores_per_replica is None:
+ raise ValueError(
+ 'input_partition_dims requires setting num_cores_per_replica.')
+
# Parse computation_shape
if num_cores_per_replica is not None:
if num_cores_per_replica not in [1, 2, 4, 8]:
@@ -139,7 +165,8 @@ class TPUConfig(
num_cores_per_replica=num_cores_per_replica,
per_host_input_for_training=per_host_input_for_training,
tpu_job_name=tpu_job_name,
- initial_infeed_sleep_secs=initial_infeed_sleep_secs)
+ initial_infeed_sleep_secs=initial_infeed_sleep_secs,
+ input_partition_dims=input_partition_dims)
class RunConfig(run_config_lib.RunConfig):
diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_context.py b/tensorflow/contrib/tpu/python/tpu/tpu_context.py
index a9cf54f77d..2c054360a4 100644
--- a/tensorflow/contrib/tpu/python/tpu/tpu_context.py
+++ b/tensorflow/contrib/tpu/python/tpu/tpu_context.py
@@ -273,6 +273,10 @@ class _InternalTPUContext(object):
return self._model_parallelism_enabled
@property
+ def input_partition_dims(self):
+ return self._config.tpu_config.input_partition_dims
+
+ @property
def device_assignment(self):
return (self._get_device_assignment()
if self._model_parallelism_enabled else None)
diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py
index 2c7e7d84c0..bd8f2c99a8 100644
--- a/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py
+++ b/tensorflow/contrib/tpu/python/tpu/tpu_estimator.py
@@ -22,6 +22,7 @@ import collections
import copy
import os
import signal
+import sys
import threading
import time
@@ -257,7 +258,10 @@ class TPUEstimatorSpec(model_fn_lib._TPUEstimatorSpec): # pylint: disable=prote
eval_metrics=None,
export_outputs=None,
scaffold_fn=None,
- host_call=None):
+ host_call=None,
+ training_hooks=None,
+ evaluation_hooks=None,
+ prediction_hooks=None):
"""Creates a validated `TPUEstimatorSpec` instance."""
host_calls = {}
if eval_metrics is not None:
@@ -265,6 +269,17 @@ class TPUEstimatorSpec(model_fn_lib._TPUEstimatorSpec): # pylint: disable=prote
if host_call is not None:
host_calls['host_call'] = host_call
_OutfeedHostCall.validate(host_calls)
+
+ training_hooks = list(training_hooks or [])
+ evaluation_hooks = list(evaluation_hooks or [])
+ prediction_hooks = list(prediction_hooks or [])
+
+ for hook in training_hooks + evaluation_hooks + prediction_hooks:
+ if not isinstance(hook, session_run_hook.SessionRunHook):
+ raise TypeError(
+ 'All hooks must be SessionRunHook instances, given: {}'.format(
+ hook))
+
return super(TPUEstimatorSpec, cls).__new__(
cls,
mode=mode,
@@ -274,7 +289,10 @@ class TPUEstimatorSpec(model_fn_lib._TPUEstimatorSpec): # pylint: disable=prote
eval_metrics=eval_metrics,
export_outputs=export_outputs,
scaffold_fn=scaffold_fn,
- host_call=host_call)
+ host_call=host_call,
+ training_hooks=training_hooks,
+ evaluation_hooks=evaluation_hooks,
+ prediction_hooks=prediction_hooks)
def as_estimator_spec(self):
"""Creates an equivalent `EstimatorSpec` used by CPU train/eval."""
@@ -290,6 +308,7 @@ class TPUEstimatorSpec(model_fn_lib._TPUEstimatorSpec): # pylint: disable=prote
hooks = None
if self.host_call is not None:
hooks = [_OutfeedHostCallHook(host_call_ret['host_call'])]
+ hooks = list(hooks or [])
scaffold = self.scaffold_fn() if self.scaffold_fn else None
return model_fn_lib.EstimatorSpec(
mode=self.mode,
@@ -299,9 +318,9 @@ class TPUEstimatorSpec(model_fn_lib._TPUEstimatorSpec): # pylint: disable=prote
eval_metric_ops=eval_metric_ops,
export_outputs=self.export_outputs,
scaffold=scaffold,
- training_hooks=hooks,
- evaluation_hooks=hooks,
- prediction_hooks=hooks)
+ training_hooks=self.training_hooks + hooks,
+ evaluation_hooks=self.evaluation_hooks + hooks,
+ prediction_hooks=self.prediction_hooks + hooks)
class _OpQueueContext(object):
@@ -762,16 +781,26 @@ def generate_per_host_v2_enqueue_ops_fn_for_host(
flattened_inputs = (
inputs_structure_recorder.flatten_features_and_labels(
features, labels))
-
control_deps.extend(flattened_inputs)
per_host_sharded_inputs.append(flattened_inputs)
- infeed_queue = tpu_feed.InfeedQueue(
- number_of_tuple_elements=len(per_host_sharded_inputs[0]))
- captured_infeed_queue.capture(infeed_queue)
+ if inputs_structure_recorder.flattened_input_dims:
+ # pylint: disable=protected-access
+ infeed_queue = tpu_feed._PartitionedInfeedQueue(
+ number_of_tuple_elements=len(per_host_sharded_inputs[0]),
+ host_id=host_id,
+ input_partition_dims=inputs_structure_recorder.flattened_input_dims,
+ device_assignment=ctx.device_assignment)
+ per_host_enqueue_ops = infeed_queue.generate_enqueue_ops(
+ per_host_sharded_inputs)
+ else:
+ infeed_queue = tpu_feed.InfeedQueue(
+ number_of_tuple_elements=len(per_host_sharded_inputs[0]))
+ per_host_enqueue_ops = infeed_queue.generate_enqueue_ops(
+ per_host_sharded_inputs,
+ tpu_ordinal_function=tpu_ordinal_function_impl)
+ captured_infeed_queue.capture(infeed_queue)
- per_host_enqueue_ops = infeed_queue.generate_enqueue_ops(
- per_host_sharded_inputs, tpu_ordinal_function=tpu_ordinal_function_impl)
return per_host_enqueue_ops
return enqueue_ops_fn, captured_infeed_queue, hooks, is_dataset
@@ -790,7 +819,15 @@ def generate_broadcast_enqueue_ops_fn(ctx, input_fn, inputs_structure_recorder,
is_dataset = inputs.is_dataset
if ctx.mode == model_fn_lib.ModeKeys.PREDICT:
- raise TypeError('Mode PREDICT not yet supported in BROADCAST mode.')
+ if not is_dataset:
+ raise TypeError(
+ 'For mode PREDICT, `input_fn` must return `Dataset` instead of '
+ '`features` and `labels`.')
+
+ inputs = _InputsWithStoppingSignals(
+ dataset=inputs.dataset,
+ batch_size=ctx.batch_size_for_input_fn,
+ add_padding=True)
if is_dataset:
hooks.append(inputs.dataset_initializer_hook())
@@ -809,6 +846,7 @@ def generate_broadcast_enqueue_ops_fn(ctx, input_fn, inputs_structure_recorder,
"""Generates enqueue ops for all the hosts."""
broadcasted_inputs = []
flattened_inputs = None # Cache result from input_fn.
+ signals = None
for host_id in xrange(num_hosts):
with ops.device(ctx.tpu_host_placement_function(host_id=host_id)):
for _ in xrange(ctx.num_of_replicas_per_host):
@@ -818,11 +856,13 @@ def generate_broadcast_enqueue_ops_fn(ctx, input_fn, inputs_structure_recorder,
# hosts).
if flattened_inputs is None:
features, labels = inputs.features_and_labels() # Calls get_next()
+ signals = inputs.signals()
+
inputs_structure_recorder.validate_and_record_structure(
- features, labels)
+ features, labels, signals)
flattened_inputs = (
inputs_structure_recorder.flatten_features_and_labels(
- features, labels))
+ features, labels, signals))
broadcasted_inputs.append(flattened_inputs)
infeed_queue = tpu_feed.InfeedQueue(
@@ -832,7 +872,14 @@ def generate_broadcast_enqueue_ops_fn(ctx, input_fn, inputs_structure_recorder,
broadcasted_inputs,
tpu_ordinal_function=tpu_ordinal_function_impl,
placement_function=device_function_impl)
- return enqueue_ops
+
+ if signals is None:
+ return enqueue_ops
+ else:
+ return {
+ 'ops': enqueue_ops,
+ 'signals': signals,
+ }
return enqueue_ops_fn, captured_infeed_queue, hooks, is_dataset
@@ -870,21 +917,68 @@ class _InputPipeline(object):
class InputsStructureRecorder(object):
"""The recorder to record inputs structure."""
- def __init__(self):
+ def __init__(self, input_partition_dims=None):
# Holds the structure of inputs
self._feature_names = []
self._label_names = []
self._has_labels = False
self._signals_helper = None
+ self._flattened_input_dims = None
+
+ if input_partition_dims:
+ # This should have been validated in TPUConfig.
+ assert len(input_partition_dims) <= 2, 'must have 1 or 2 elements.'
+ if len(input_partition_dims) == 2:
+ self._feature_dims, self._label_dims = input_partition_dims
+ else:
+ self._feature_dims = input_partition_dims[0]
+ self._label_dims = None
+
+ assert self._feature_dims is not None, ('input_partition_dims[0] must '
+ 'not be None')
+ else:
+ self._feature_dims = None
+ self._label_dims = None
# Internal state.
self._initialized = False
+ @property
+ def flattened_input_dims(self):
+ assert self._initialized, 'InputsStructureRecorder is not initialized.'
+ return self._flattened_input_dims
+
def has_labels(self):
return self._has_labels
+ def _flatten_input_dims(self, feature_dims, feature_dims_names, label_dims,
+ label_dims_names, label_names, has_labels):
+ """Flatten input dims with the same order as flattened input tensors."""
+ flattened_input_dims = []
+ if feature_dims_names:
+ # We need a fixed ordering for matching the tensors in features.
+ flattened_input_dims.extend(
+ [feature_dims[name] for name in feature_dims_names])
+ else:
+ flattened_input_dims.append(feature_dims)
+
+ if label_dims_names:
+ # We need a fixed ordering for matching the tensors in labels.
+ flattened_input_dims.extend(
+ [label_dims[name] for name in label_dims_names])
+ else:
+ if label_names:
+ num_tensors_in_label = len(label_names)
+ else:
+ num_tensors_in_label = int(has_labels)
+ # Setting `None` in input_partition_dims[1] will apply `None` to
+ # all the tensors in labels, regardless of internal structure.
+ flattened_input_dims.extend([label_dims] * num_tensors_in_label)
+
+ return flattened_input_dims
+
def validate_and_record_structure(self, features, labels, signals=None):
- """Validates and records the structure of features` and `labels`."""
+ """Validates and records the structure of `features` and `labels`."""
def _extract_key_names(tensor_or_dict):
if tensor_or_dict is None:
@@ -912,6 +1006,24 @@ class _InputPipeline(object):
self._feature_names = feature_names
self._label_names = label_names
self._has_labels = has_labels
+ if self._feature_dims is not None:
+ feature_dims_names = _extract_key_names(self._feature_dims)
+ if feature_dims_names != feature_names:
+ raise ValueError(
+ 'TPUConfig.input_partition_dims[0] mismatched feature'
+ ' keys. Expected {}, got {}'.format(feature_names,
+ feature_dims_names))
+
+ label_dims_names = _extract_key_names(self._label_dims)
+ if self._label_dims is not None and label_dims_names != label_names:
+ raise ValueError(
+ 'TPUConfig.input_partition_dims[1] mismatched label'
+ ' keys. Expected {}, got {}'.format(label_names,
+ label_dims_names))
+
+ self._flattened_input_dims = self._flatten_input_dims(
+ self._feature_dims, feature_dims_names, self._label_dims,
+ label_dims_names, label_names, has_labels)
def flatten_features_and_labels(self, features, labels, signals=None):
"""Flattens the `features` and `labels` to a single tensor list."""
@@ -1006,7 +1118,8 @@ class _InputPipeline(object):
Raises:
ValueError: If both `sharded_features` and `num_cores` are `None`.
"""
- self._inputs_structure_recorder = _InputPipeline.InputsStructureRecorder()
+ self._inputs_structure_recorder = _InputPipeline.InputsStructureRecorder(
+ ctx.input_partition_dims)
self._sharded_per_core = ctx.is_input_sharded_per_core()
self._input_fn = input_fn
@@ -1079,9 +1192,11 @@ class _InputPipeline(object):
all_hooks.extend(hooks)
if is_dataset:
run_infeed_loop_on_coordinator = False
- enqueue_ops.append(
- _wrap_computation_in_while_loop(
- device=host_device, op_fn=enqueue_ops_fn))
+ wrap_fn = (
+ _wrap_computation_in_while_loop
+ if self._ctx.mode != model_fn_lib.ModeKeys.PREDICT else
+ _wrap_computation_in_while_loop_with_stopping_signals)
+ enqueue_ops.append(wrap_fn(device=host_device, op_fn=enqueue_ops_fn))
else:
enqueue_ops.append(enqueue_ops_fn())
infeed_queues.append(captured_infeed_queue.get())
@@ -1199,6 +1314,7 @@ class _ModelFnWrapper(object):
host_call = _OutfeedHostCall(self._ctx)
captured_scaffold_fn = _CapturedObject()
+ captured_training_hooks = _CapturedObject()
def train_step(loss):
"""Training step function for use inside a while loop."""
@@ -1215,6 +1331,8 @@ class _ModelFnWrapper(object):
else:
captured_scaffold_fn.capture(None)
+ captured_training_hooks.capture(estimator_spec.training_hooks)
+
# We must run train_op to update the variables prior to running the
# outfeed.
with ops.control_dependencies([train_op]):
@@ -1226,7 +1344,8 @@ class _ModelFnWrapper(object):
with ops.control_dependencies(host_call_outfeed_ops):
return array_ops.identity(loss)
- return train_step, host_call, captured_scaffold_fn
+ return (train_step, host_call, captured_scaffold_fn,
+ captured_training_hooks)
def convert_to_single_tpu_eval_step(self, dequeue_fn):
"""Converts user provided model_fn` as a single eval step on TPU.
@@ -1256,6 +1375,7 @@ class _ModelFnWrapper(object):
"""
host_calls = _OutfeedHostCall(self._ctx)
captured_scaffold_fn = _CapturedObject()
+ captured_eval_hooks = _CapturedObject()
def eval_step(total_loss):
"""Evaluation step function for use inside a while loop."""
@@ -1270,6 +1390,8 @@ class _ModelFnWrapper(object):
loss = tpu_estimator_spec.loss
captured_scaffold_fn.capture(tpu_estimator_spec.scaffold_fn)
+ captured_eval_hooks.capture(tpu_estimator_spec.evaluation_hooks)
+
to_record = {}
if tpu_estimator_spec.eval_metrics:
to_record['eval_metrics'] = tpu_estimator_spec.eval_metrics
@@ -1282,7 +1404,7 @@ class _ModelFnWrapper(object):
with ops.control_dependencies(host_calls.create_enqueue_op()):
return math_ops.add(total_loss, loss)
- return eval_step, host_calls, captured_scaffold_fn
+ return eval_step, host_calls, captured_scaffold_fn, captured_eval_hooks
def convert_to_single_tpu_predict_step(self, dequeue_fn):
"""Converts user provided model_fn` as a single predict step on TPU.
@@ -1297,6 +1419,7 @@ class _ModelFnWrapper(object):
"""
host_calls = _OutfeedHostCall(self._ctx)
captured_scaffold_fn = _CapturedObject()
+ captured_predict_hooks = _CapturedObject()
def predict_step(unused_scalar_stopping_signal):
"""Evaluation step function for use inside a while loop."""
@@ -1317,6 +1440,7 @@ class _ModelFnWrapper(object):
self._verify_tpu_spec_predictions(tpu_estimator_spec.predictions)
captured_scaffold_fn.capture(tpu_estimator_spec.scaffold_fn)
+ captured_predict_hooks.capture(tpu_estimator_spec.prediction_hooks)
to_record = {}
identity_fn = lambda **kwargs: kwargs
to_record['predictions'] = [identity_fn, tpu_estimator_spec.predictions]
@@ -1328,7 +1452,8 @@ class _ModelFnWrapper(object):
with ops.control_dependencies(host_calls.create_enqueue_op()):
return _StopSignals.as_scalar_stopping_signal(stopping_signals)
- return predict_step, host_calls, captured_scaffold_fn
+ return (predict_step, host_calls, captured_scaffold_fn,
+ captured_predict_hooks)
def _verify_tpu_spec_predictions(self, predictions):
"""Validates TPUEstimatorSpec.predictions dict."""
@@ -1450,11 +1575,9 @@ class _ModelFnWrapper(object):
err_msg = '{} returned by EstimatorSpec is not supported in TPUEstimator.'
if estimator_spec.training_chief_hooks:
- raise ValueError(err_msg.format('training_chief_hooks'))
- if estimator_spec.training_hooks:
- raise ValueError(err_msg.format('training_hooks'))
- if estimator_spec.evaluation_hooks:
- raise ValueError(err_msg.format('evaluation_hooks'))
+ raise ValueError(
+ err_msg.format('training_chief_hooks') + 'If you want' +
+ ' to pass training hooks, please pass via training_hooks.')
if estimator_spec.scaffold:
logging.warning('EstimatorSpec.Scaffold is ignored by TPU train/eval. '
@@ -1936,10 +2059,9 @@ class TPUEstimator(estimator_lib.Estimator):
"""Constructs an `TPUEstimator` instance.
Args:
- model_fn: Model function as required by `Estimator`. For training, the
- returned `EstimatorSpec` cannot have hooks as it is not supported in
- `TPUEstimator`. Instead, the user can pass the training hooks as
- an argument to `TPUEstimator.train()`.
+ model_fn: Model function as required by `Estimator` which returns
+ EstimatorSpec or TPUEstimatorSpec. `training_hooks`, 'evaluation_hooks',
+ and `prediction_hooks` must not capure any TPU Tensor inside the model_fn.
model_dir: Directory to save model parameters, graph and etc. This can
also be used to load checkpoints from the directory into a estimator to
continue training a previously saved model. If `None`, the model_dir in
@@ -2308,8 +2430,8 @@ class TPUEstimator(estimator_lib.Estimator):
input_fn=input_fn, hooks=hooks, steps=steps, max_steps=max_steps,
saving_listeners=saving_listeners
)
- except Exception as e: # pylint: disable=broad-except
- rendezvous.record_error('training_loop', e)
+ except Exception: # pylint: disable=broad-except
+ rendezvous.record_error('training_loop', sys.exc_info())
finally:
rendezvous.record_done('training_loop')
rendezvous.raise_errors()
@@ -2323,8 +2445,8 @@ class TPUEstimator(estimator_lib.Estimator):
input_fn, steps=steps, hooks=hooks, checkpoint_path=checkpoint_path,
name=name
)
- except Exception as e: # pylint: disable=broad-except
- rendezvous.record_error('evaluation_loop', e)
+ except Exception: # pylint: disable=broad-except
+ rendezvous.record_error('evaluation_loop', sys.exc_info())
finally:
rendezvous.record_done('evaluation_loop')
rendezvous.raise_errors()
@@ -2345,8 +2467,8 @@ class TPUEstimator(estimator_lib.Estimator):
checkpoint_path=checkpoint_path,
yield_single_examples=yield_single_examples):
yield result
- except Exception as e: # pylint: disable=broad-except
- rendezvous.record_error('prediction_loop', e)
+ except Exception: # pylint: disable=broad-except
+ rendezvous.record_error('prediction_loop', sys.exc_info())
finally:
rendezvous.record_done('prediction_loop')
rendezvous.raise_errors()
@@ -2408,7 +2530,7 @@ class TPUEstimator(estimator_lib.Estimator):
graph.add_to_collection(_TPU_ENQUEUE_OPS, enqueue_op)
if mode == model_fn_lib.ModeKeys.TRAIN:
- loss, host_call, scaffold = (
+ loss, host_call, scaffold, training_hooks = (
_train_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn))
host_ops = host_call.create_tpu_hostcall()
if host_ops is None:
@@ -2463,6 +2585,9 @@ class TPUEstimator(estimator_lib.Estimator):
self._config.tpu_config.iterations_per_loop)
hooks.append(examples_hook)
+ if training_hooks:
+ hooks.extend(training_hooks)
+
chief_hooks = []
if (self._config.save_checkpoints_secs or
self._config.save_checkpoints_steps):
@@ -2474,6 +2599,7 @@ class TPUEstimator(estimator_lib.Estimator):
checkpoint_hook._set_steps_per_run( # pylint: disable=protected-access
self._config.tpu_config.iterations_per_loop)
chief_hooks.append(checkpoint_hook)
+
summary.scalar(model_fn_lib.LOSS_METRIC_KEY, loss)
with ops.control_dependencies([loss]):
update_ops = _sync_variables_ops()
@@ -2493,7 +2619,7 @@ class TPUEstimator(estimator_lib.Estimator):
scaffold=scaffold)
if mode == model_fn_lib.ModeKeys.EVAL:
- total_loss, host_calls, scaffold = _eval_on_tpu_system(
+ total_loss, host_calls, scaffold, eval_hooks = _eval_on_tpu_system(
ctx, model_fn_wrapper, dequeue_fn)
iterations_per_loop_var = _create_or_get_iterations_per_loop()
mean_loss = math_ops.div(total_loss,
@@ -2537,6 +2663,9 @@ class TPUEstimator(estimator_lib.Estimator):
rendezvous=self._rendezvous[mode]),
] + input_hooks
+ if eval_hooks:
+ hooks.extend(eval_hooks)
+
return model_fn_lib.EstimatorSpec(
mode,
loss=mean_loss,
@@ -2547,8 +2676,9 @@ class TPUEstimator(estimator_lib.Estimator):
# Predict
assert mode == model_fn_lib.ModeKeys.PREDICT
- dummy_predict_op, host_calls, scaffold = _predict_on_tpu_system(
- ctx, model_fn_wrapper, dequeue_fn)
+ (dummy_predict_op, host_calls,
+ scaffold, prediction_hooks) = _predict_on_tpu_system(
+ ctx, model_fn_wrapper, dequeue_fn)
with ops.control_dependencies([dummy_predict_op]):
internal_ops_to_run = _sync_variables_ops()
with ops.control_dependencies(internal_ops_to_run):
@@ -2604,6 +2734,9 @@ class TPUEstimator(estimator_lib.Estimator):
ctx, enqueue_ops, host_ops, rendezvous=self._rendezvous[mode]),
] + input_hooks
+ if prediction_hooks:
+ hooks.extend(prediction_hooks)
+
return model_fn_lib.EstimatorSpec(
mode,
prediction_hooks=hooks,
@@ -2687,8 +2820,8 @@ def _eval_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn):
"""Executes `model_fn_wrapper` multiple times on all TPU shards."""
iterations_per_loop_var = _create_or_get_iterations_per_loop()
- single_tpu_eval_step, host_calls, captured_scaffold_fn = (
- model_fn_wrapper.convert_to_single_tpu_eval_step(dequeue_fn))
+ (single_tpu_eval_step, host_calls, captured_scaffold_fn, captured_eval_hooks
+ ) = model_fn_wrapper.convert_to_single_tpu_eval_step(dequeue_fn)
def multi_tpu_eval_steps_on_single_shard():
return training_loop.repeat(
@@ -2703,15 +2836,16 @@ def _eval_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn):
device_assignment=ctx.device_assignment)
scaffold = _get_scaffold(captured_scaffold_fn)
- return loss, host_calls, scaffold
+ return loss, host_calls, scaffold, captured_eval_hooks.get()
def _train_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn):
"""Executes `model_fn_wrapper` multiple times on all TPU shards."""
iterations_per_loop_var = _create_or_get_iterations_per_loop()
- single_tpu_train_step, host_call, captured_scaffold_fn = (
- model_fn_wrapper.convert_to_single_tpu_train_step(dequeue_fn))
+ (single_tpu_train_step, host_call, captured_scaffold_fn,
+ captured_training_hooks) = (
+ model_fn_wrapper.convert_to_single_tpu_train_step(dequeue_fn))
def multi_tpu_train_steps_on_single_shard():
return training_loop.repeat(
@@ -2726,15 +2860,16 @@ def _train_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn):
device_assignment=ctx.device_assignment)
scaffold = _get_scaffold(captured_scaffold_fn)
- return loss, host_call, scaffold
+ return loss, host_call, scaffold, captured_training_hooks.get()
def _predict_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn):
"""Executes `model_fn_wrapper` multiple times on all TPU shards."""
num_cores = ctx.num_cores
- single_tpu_predict_step, host_calls, captured_scaffold_fn = (
- model_fn_wrapper.convert_to_single_tpu_predict_step(dequeue_fn))
+ (single_tpu_predict_step, host_calls, captured_scaffold_fn,
+ captured_predict_hooks
+ ) = model_fn_wrapper.convert_to_single_tpu_predict_step(dequeue_fn)
def multi_tpu_predict_steps_on_single_shard():
@@ -2754,7 +2889,7 @@ def _predict_on_tpu_system(ctx, model_fn_wrapper, dequeue_fn):
outputs_from_all_shards=False)
scaffold = _get_scaffold(captured_scaffold_fn)
- return dummy_predict_op, host_calls, scaffold
+ return dummy_predict_op, host_calls, scaffold, captured_predict_hooks.get()
def _wrap_computation_in_while_loop(device, op_fn):
diff --git a/tensorflow/contrib/tpu/python/tpu/tpu_feed.py b/tensorflow/contrib/tpu/python/tpu/tpu_feed.py
index a44b4f4622..d9c77a3ea1 100644
--- a/tensorflow/contrib/tpu/python/tpu/tpu_feed.py
+++ b/tensorflow/contrib/tpu/python/tpu/tpu_feed.py
@@ -20,8 +20,13 @@ from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
+import itertools
+
+import numpy as np
from six.moves import xrange # pylint: disable=redefined-builtin
+from tensorflow.compiler.xla.experimental.xla_sharding import xla_sharding
+from tensorflow.compiler.xla.python_api import xla_shape
from tensorflow.contrib.tpu.python.ops import tpu_ops
from tensorflow.contrib.tpu.python.tpu import tpu
from tensorflow.contrib.tpu.python.tpu import tpu_sharding
@@ -30,6 +35,7 @@ from tensorflow.python.framework import dtypes
from tensorflow.python.framework import ops
from tensorflow.python.framework import tensor_shape
from tensorflow.python.ops import array_ops
+from tensorflow.python.util import nest
class InfeedQueue(object):
@@ -640,3 +646,264 @@ class InfeedQueue(object):
tpu_ordinal=tpu_ordinal_function(index))
for (shard, index) in zip(sharded_inputs, xrange(self.number_of_shards))
]
+
+
+class _PartitionedInfeedQueue(InfeedQueue):
+ """A helper object to build a device infeed queue with input partition.
+
+ Args:
+ number_of_tuple_elements: the number of Tensors fed atomically through the
+ queue, must be present unless it can be inferred from other arguments.
+ device_assignment: A TPU `DeviceAssignment` which is used to place all the
+ partitions to different TPU infeed queues.
+ host_id: The id of the host machine.
+ input_partition_dims: A nested list/tuple of integers. Each inner
+ list/tuple describes how to partition the corresponding input tensor.
+ tuple_types: If not None, a list of types of the elements of the queue.
+ tuple_shapes: If not None, a list of shapes of the elements of the queue.
+ name: The name of the queue.
+ """
+
+ def __init__(self,
+ number_of_tuple_elements,
+ device_assignment,
+ host_id,
+ input_partition_dims=None,
+ tuple_types=None,
+ tuple_shapes=None,
+ name=None):
+ super(_PartitionedInfeedQueue, self).__init__(
+ number_of_tuple_elements=number_of_tuple_elements,
+ tuple_types=tuple_types,
+ tuple_shapes=None,
+ shard_dimensions=None,
+ name="PartitionedInfeedQueue" if name is None else name)
+ self._input_partition_dims = input_partition_dims
+ self._host_id = host_id
+ self._device_assignment = device_assignment
+
+ def generate_dequeue_op(self, tpu_device=0):
+ """Generate TPU dequeue ops.
+
+ Args:
+ tpu_device: The TPU device ordinal where the infeed instruction should be
+ placed.
+
+ Returns:
+ A list of Outputs corresponding to a partition of infeed dequeued
+ into XLA, suitable for use within a replicated block.
+
+ Raises:
+ ValueError: if the types or shapes of the tuple elements have not been
+ set; or if a dequeue op has already been generated.
+ """
+ self.freeze()
+ if self._generated_dequeue_op:
+ raise ValueError("Can't generate two dequeue Ops from the same queue")
+ self._generated_dequeue_op = True
+ full_name = "%s/dequeue" % self._name
+ sharded_shapes = [
+ policy.get_sharded_shape(shape)
+ for (shape, policy) in zip(self._tuple_shapes, self._sharding_policies)
+ ]
+ with ops.device(tpu.core(tpu_device)):
+ values = tpu_ops.infeed_dequeue_tuple(
+ dtypes=self._tuple_types, shapes=sharded_shapes, name=full_name)
+ return self._tag_sharding_attribute_for_dequeued_tensors(
+ values, self._input_partition_dims)
+
+ def generate_enqueue_ops(self, per_host_sharded_inputs):
+ """Generates the host-side Ops to enqueue the partitioned inputs.
+
+ per_host_sharded_inputs is a list, one for each replica, of lists of
+ Tensors. sharded_inputs[i] is the tuple of Tensors to use to feed
+ replica i.
+ sharded_inputs[i][j] is partitioned by self._input_partition_dims[j].
+
+ For example, if sharded_inputs[i][j] is a 2-D Tensor:
+ [[A, B, C, D],
+ [E ,F, G, H]]
+ self._input_partition_dims[j] is [2, 4].
+
+ sharded_inputs[i][j] will be partitioned and flattened into:
+ [A, B, C, D, E, F, G, H] and fed into the logical core ids:
+ [0, 1, 2, 3, 4, 5, 6, 7] respectively.
+
+ Args:
+ per_host_sharded_inputs: a list of lists of Tensors. The length of the
+ outer list determines the number of shards. Each inner list indicates
+ the types and shapes of the tuples in the corresponding shard.
+
+ Returns:
+ A list of host-side Ops, one for each shard, that when executed together
+ will enqueue a full-size element of infeed.
+
+ Raises:
+ ValueError: if the queue configuration has previously been frozen and the
+ shapes of the elements of sharded_inputs are not compatible with the
+ frozen configuration; or if the shapes of the elements of sharded_inputs
+ don't form a consistent unsharded tuple; or if the elements of a tuple
+ have different device constraints; or if the partition dims are invalid.
+ TypeError: if the queue configuration has previously been frozen and the
+ types of the elements of sharded_inputs are not compatible with the
+ frozen configuration; or if the types of the elements of sharded_inputs
+ don't form a consistent unsharded tuple.
+ """
+ self.set_configuration_from_sharded_input_tensors(per_host_sharded_inputs)
+ number_of_replicas_per_host = len(per_host_sharded_inputs)
+ number_of_tuple_elements = len(per_host_sharded_inputs[0])
+
+ assert len(self._input_partition_dims) == number_of_tuple_elements
+ per_host_enqueue_ops = []
+
+ for replica_index in range(number_of_replicas_per_host):
+ flattened_inputs = per_host_sharded_inputs[replica_index]
+ inputs_part_dims_flat = nest.flatten_up_to(flattened_inputs,
+ self._input_partition_dims)
+ inputs_parted_iters = [
+ iter(self._partition_or_replicate_on_host(x, dims)) for x, dims in
+ zip(per_host_sharded_inputs[replica_index], inputs_part_dims_flat)
+ ]
+
+ for core_index in xrange(self._device_assignment.num_cores_per_replica):
+ # Places different partitions to different logic cores.
+ logical_core = self._get_logical_core(core_index)
+ replica_id = self._device_assignment.lookup_replicas(
+ self._host_id, logical_core)[replica_index]
+ ordinal = self._device_assignment.tpu_ordinal(
+ replica=replica_id, logical_core=logical_core)
+ infeed_inputs = []
+ for it in inputs_parted_iters:
+ input_for_device = next(it, None)
+ if input_for_device is not None:
+ infeed_inputs.append(input_for_device)
+
+ if infeed_inputs:
+ per_host_enqueue_ops.append(
+ tpu_ops.infeed_enqueue_tuple(
+ inputs=infeed_inputs,
+ shapes=[x.shape for x in infeed_inputs],
+ name="enqueue/replica_{0}/input_{1}".format(
+ replica_index, core_index),
+ device_ordinal=ordinal))
+ return per_host_enqueue_ops
+
+ def _check_input_partition_dims(self, tensor, dims):
+ """Checks that input partition dims are valid for the `Tensor`.
+
+ Args:
+ tensor: Input tensor for partitioning.
+ dims: A list of integer describes how to partition the input tensor.
+
+ Raises:
+ ValueError: If the tensor can't be partitioned by dims or the
+ num_cores_per_replica doesn't match the number of
+ partitions(dims.prod()).
+ """
+ if dims is None:
+ return
+
+ dims = np.array(dims)
+
+ if (dims < 1).any():
+ raise ValueError("All input partition dims must be >= 1.")
+
+ # No partitioning, so don't perform further checks.
+ if dims.prod() == 1:
+ return
+
+ if dims.prod() != self._device_assignment.num_cores_per_replica:
+ raise ValueError(
+ "The product of each input parition dim should equal to "
+ "num_cores_per_replica. (dim = {}, num_cores_per_replica "
+ "= {})".format(dims, self._device_assignment.num_cores_per_replica))
+ if dims.shape[0] != tensor.shape.ndims:
+ raise ValueError(
+ "Input partition dims must have the same number of dimensions "
+ "as the `Tensor` to be partitioned. (tensor shape = {}, input "
+ "partition dims = {}).".format(tensor.shape.as_list(), dims))
+
+ tensor.shape.assert_is_fully_defined()
+ if (np.array(tensor.shape.as_list()) % dims != 0).any():
+ raise ValueError(
+ "All input partition dims must divide exactly into the `Tensor` "
+ "shape (tensor shape = {}, input partition dims = {}).".format(
+ tensor.shape.as_list(), dims))
+
+ def _partition_or_replicate_on_host(self, tensor, dims):
+ """Partitions or replicates the input tensor.
+
+ The ops inside this function are placed on the host side.
+
+ Args:
+ tensor: The input tensor which will be partioned or replicated.
+ dims: A list of integer describes how to partition the input tensor.
+ Returns:
+ An iterator of `Tensor`s or a list of partioned tensors.
+ """
+ self._check_input_partition_dims(tensor, dims)
+ if dims is None:
+ return itertools.repeat(tensor)
+ else:
+ output = [tensor]
+ for axis, dim in enumerate(dims):
+ if dim > 1:
+ output = [array_ops.split(x, dim, axis=axis) for x in output]
+ output = nest.flatten(output)
+ return output
+
+ def _tag_sharding_attribute_for_dequeued_tensor(self, tensor, dims):
+ """Tags appropriate XLA sharding attribute to the dequeued tensor.
+
+ Args:
+ tensor: The dequeued tensor on TPU.
+ dims: A list of integer describes how the tensor is partitioned.
+
+ Returns:
+ The same tensor with the xla_sharding attribute.
+ """
+ if dims is None:
+ return xla_sharding.replicate(tensor)
+ elif np.prod(dims) == 1:
+ return xla_sharding.assign_device(tensor, 0)
+ else:
+ tile_shape = np.array(tensor.shape.as_list()) // dims
+ tile_assignment = np.arange(np.prod(dims)).reshape(dims)
+ return xla_sharding.tile(
+ tensor=tensor,
+ tile_shape=xla_shape.CreateShapeFromDtypeAndTuple(
+ dtype=np.dtype(tensor.dtype.as_numpy_dtype),
+ shape_tuple=tile_shape),
+ tile_assignment=tile_assignment)
+
+ def _tag_sharding_attribute_for_dequeued_tensors(self, dequeues, dims):
+ """Tags appropriate XLA sharding attribute to the dequeued tensors.
+
+ Args:
+ dequeues: A list of dequeued tensors on TPU.
+ dims: A list of integer describes how the tensor is partitioned.
+
+ Returns:
+ The same dequeues with appropriate xla_sharding attribute.
+ """
+ nest.assert_shallow_structure(dequeues, dims)
+ return nest.map_structure_up_to(
+ dequeues, self._tag_sharding_attribute_for_dequeued_tensor, dequeues,
+ dims)
+
+ def _get_logical_core(self, core_index):
+ """Maps the core index to the 3D coordinate within replica.
+
+ The lowest dimension number in computation_shape is the slowest varying
+ dimension (most major).
+
+ Args:
+ core_index: An integer represents the core index within replcia.
+
+ Returns:
+ A tuple with three integers which represents the 3D coordinate.
+ """
+ computation_shape = self._device_assignment.computation_shape
+ return (core_index // (computation_shape[1] * computation_shape[2]),
+ core_index % (computation_shape[1] * computation_shape[2]) //
+ computation_shape[2], core_index % computation_shape[2])
diff --git a/tensorflow/core/BUILD b/tensorflow/core/BUILD
index e63327b5d4..326b040ad8 100644
--- a/tensorflow/core/BUILD
+++ b/tensorflow/core/BUILD
@@ -2520,7 +2520,7 @@ tf_cuda_library(
"//third_party/mkl:intel_binary_blob",
"@mkl_dnn",
],
- ) ,
+ ),
alwayslink = 1,
)
@@ -2928,6 +2928,14 @@ tf_cuda_library(
)
cc_library(
+ name = "session_ref",
+ srcs = ["common_runtime/session_ref.cc"],
+ hdrs = ["common_runtime/session_ref.h"],
+ copts = tf_copts(),
+ deps = [":core_cpu_base"],
+)
+
+cc_library(
name = "gpu_id",
hdrs = [
"common_runtime/gpu/gpu_id.h",
@@ -3485,6 +3493,7 @@ tf_cc_tests(
"framework/tensor_shape_test.cc",
"framework/tensor_slice_test.cc",
"framework/tensor_test.cc",
+ "framework/tensor_testutil_test.cc",
"framework/tensor_util_test.cc",
"framework/tracking_allocator_test.cc",
"framework/types_test.cc",
diff --git a/tensorflow/core/api_def/base_api/api_def_NonMaxSuppressionV4.pbtxt b/tensorflow/core/api_def/base_api/api_def_NonMaxSuppressionV4.pbtxt
new file mode 100644
index 0000000000..75df90f570
--- /dev/null
+++ b/tensorflow/core/api_def/base_api/api_def_NonMaxSuppressionV4.pbtxt
@@ -0,0 +1,78 @@
+op {
+ graph_op_name: "NonMaxSuppressionV4"
+ in_arg {
+ name: "boxes"
+ description: <<END
+A 2-D float tensor of shape `[num_boxes, 4]`.
+END
+ }
+ in_arg {
+ name: "scores"
+ description: <<END
+A 1-D float tensor of shape `[num_boxes]` representing a single
+score corresponding to each box (each row of boxes).
+END
+ }
+ in_arg {
+ name: "max_output_size"
+ description: <<END
+A scalar integer tensor representing the maximum number of
+boxes to be selected by non max suppression.
+END
+ }
+ in_arg {
+ name: "iou_threshold"
+ description: <<END
+A 0-D float tensor representing the threshold for deciding whether
+boxes overlap too much with respect to IOU.
+END
+ }
+ in_arg {
+ name: "score_threshold"
+ description: <<END
+A 0-D float tensor representing the threshold for deciding when to remove
+boxes based on score.
+END
+ }
+ attr {
+ name: "pad_to_max_output_size"
+ description: <<END
+If true, the output `selected_indices` is padded to be of length
+`max_output_size`. Defaults to false.
+END
+ }
+ out_arg {
+ name: "selected_indices"
+ description: <<END
+A 1-D integer tensor of shape `[M]` representing the selected
+indices from the boxes tensor, where `M <= max_output_size`.
+END
+ }
+ out_arg {
+ name: "valid_outputs"
+ description: <<END
+A 0-D integer tensor representing the number of valid elements in
+`selected_indices`, with the valid elements appearing first.
+END
+ }
+ summary: "Greedily selects a subset of bounding boxes in descending order of score,"
+ description: <<END
+pruning away boxes that have high intersection-over-union (IOU) overlap
+with previously selected boxes. Bounding boxes with score less than
+`score_threshold` are removed. Bounding boxes are supplied as
+[y1, x1, y2, x2], where (y1, x1) and (y2, x2) are the coordinates of any
+diagonal pair of box corners and the coordinates can be provided as normalized
+(i.e., lying in the interval [0, 1]) or absolute. Note that this algorithm
+is agnostic to where the origin is in the coordinate system and more
+generally is invariant to orthogonal transformations and translations
+of the coordinate system; thus translating or reflections of the coordinate
+system result in the same boxes being selected by the algorithm.
+The output of this operation is a set of integers indexing into the input
+collection of bounding boxes representing the selected boxes. The bounding
+box coordinates corresponding to the selected indices can then be obtained
+using the `tf.gather operation`. For example:
+ selected_indices = tf.image.non_max_suppression_v2(
+ boxes, scores, max_output_size, iou_threshold, score_threshold)
+ selected_boxes = tf.gather(boxes, selected_indices)
+END
+}
diff --git a/tensorflow/core/api_def/python_api/api_def_NonMaxSuppressionV4.pbtxt b/tensorflow/core/api_def/python_api/api_def_NonMaxSuppressionV4.pbtxt
new file mode 100644
index 0000000000..be6caacd00
--- /dev/null
+++ b/tensorflow/core/api_def/python_api/api_def_NonMaxSuppressionV4.pbtxt
@@ -0,0 +1,4 @@
+op {
+ graph_op_name: "NonMaxSuppressionV4"
+ visibility: HIDDEN
+}
diff --git a/tensorflow/core/common_runtime/broadcaster.cc b/tensorflow/core/common_runtime/broadcaster.cc
index 46142d5923..e1c6b21939 100644
--- a/tensorflow/core/common_runtime/broadcaster.cc
+++ b/tensorflow/core/common_runtime/broadcaster.cc
@@ -27,13 +27,14 @@ namespace tensorflow {
namespace {
// Key to be used for BufRendezvous by Broadcaster.
-string BroadcastBufKey(const string& exec_key, int src_rank, int dst_rank) {
+string BroadcastBufKey(const string& exec_key, int subdiv, int src_rank,
+ int dst_rank) {
if (READABLE_KEYS) {
- return strings::StrCat("broadcast(", exec_key, "):src(", src_rank, "):dst(",
- dst_rank, ")");
+ return strings::StrCat("broadcast(", exec_key, "):subdiv(", subdiv,
+ "):src(", src_rank, "):dst(", dst_rank, ")");
} else {
// TODO(tucker): Try a denser format, e.g. a 64 or 128 bit hash.
- return strings::StrCat(exec_key, ":", src_rank, ":", dst_rank);
+ return strings::StrCat(exec_key, ":", subdiv, ":", src_rank, ":", dst_rank);
}
}
} // namespace
@@ -85,11 +86,15 @@ void Broadcaster::Run(StatusCallback done) {
// device, no send to it is necessary.
/* static*/
-int Broadcaster::TreeRecvFrom(const CollectiveParams& cp) {
- DCHECK_EQ(1, cp.subdiv_rank.size());
- if (cp.is_source) return -1;
- int source_rank = cp.instance.impl_details.subdiv_source_rank[0];
- int my_rank = cp.subdiv_rank[0];
+int Broadcaster::TreeRecvFrom(const CollectiveParams& cp, int subdiv) {
+ DCHECK_LT(subdiv, static_cast<int>(cp.subdiv_rank.size()));
+ int my_rank = cp.subdiv_rank[subdiv];
+ if (-1 == my_rank) return -1;
+
+ const auto& impl = cp.instance.impl_details;
+ DCHECK_LT(subdiv, static_cast<int>(impl.subdiv_source_rank.size()));
+ int source_rank = impl.subdiv_source_rank[subdiv];
+ if (my_rank == source_rank) return -1;
if (source_rank == 0) {
return (my_rank - 1) / 2;
} else {
@@ -99,13 +104,24 @@ int Broadcaster::TreeRecvFrom(const CollectiveParams& cp) {
}
/* static */
-void Broadcaster::TreeSendTo(const CollectiveParams& cp,
+void Broadcaster::TreeSendTo(const CollectiveParams& cp, int subdiv,
std::vector<int>* targets) {
- DCHECK_EQ(1, cp.subdiv_rank.size());
+ DCHECK_LT(subdiv, static_cast<int>(cp.subdiv_rank.size()));
+ int my_rank = cp.subdiv_rank[subdiv];
+ if (-1 == my_rank) return;
+
+ const auto& impl = cp.instance.impl_details;
+ DCHECK_LT(subdiv, static_cast<int>(impl.subdiv_source_rank.size()));
+ int source_rank = impl.subdiv_source_rank[subdiv];
+
+ int group_size = 0;
+ for (int i = 0; i < impl.subdiv_permutations[subdiv].size(); i++) {
+ if (impl.subdiv_permutations[subdiv][i] >= 0) {
+ group_size++;
+ }
+ }
+
targets->clear();
- int my_rank = cp.subdiv_rank[0];
- DCHECK_EQ(1, cp.instance.impl_details.subdiv_source_rank.size());
- int source_rank = cp.instance.impl_details.subdiv_source_rank[0];
int successor_rank = 0;
if (source_rank == 0) {
successor_rank = (2 * my_rank) + 1;
@@ -116,108 +132,147 @@ void Broadcaster::TreeSendTo(const CollectiveParams& cp,
if (cp.is_source && source_rank != 0) {
// The source sends to rank 0,1 in addition to its positional
// descendants.
- if (cp.group.group_size > 1) {
+ if (group_size > 1) {
targets->push_back(0);
}
- if (cp.group.group_size > 2 && source_rank != 1) {
+ if (group_size > 2 && source_rank != 1) {
targets->push_back(1);
}
}
for (int i = 0; i < 2; ++i) {
- if (successor_rank < cp.group.group_size && successor_rank != source_rank) {
+ if (successor_rank < group_size && successor_rank != source_rank) {
targets->push_back(successor_rank);
}
++successor_rank;
}
}
-// Execute a tree broadcast, i.e. each non-source device receives from
-// one other and sends to up-to two others.
+// Executes a hierarchical tree broadcast.
+// Each subdiv is a broadcast between a subset of the devices.
+// If there is only one task, there is one subdiv comprising a broadcast between
+// all devices belonging to the task.
+// If there are n tasks, n>1, then there are n+1 subdivs. In the first (global)
+// subdiv, one device from each task participates in a binary tree broadcast.
+// Each task receives a copy of the tensor on one device via this broadcast.
+// Subsequent subdivs correspond to intra-task broadcasts. Subdiv i+1
+// corresponds to broadcast between all devices on task i. Thus, each task
+// participates in at most 2 subdivs.
void Broadcaster::RunTree() {
- mutex mu; // also guards status_ while callbacks are pending
- int pending_count = 0; // GUARDED_BY(mu)
- condition_variable all_done;
- std::vector<int> send_to_ranks;
- TreeSendTo(col_params_, &send_to_ranks);
-
- if (!is_source_) {
- // Begin by receiving the value.
- int recv_from_rank = TreeRecvFrom(col_params_);
- Notification note;
- DispatchRecv(recv_from_rank, output_,
- [this, recv_from_rank, &mu, &note](const Status& s) {
- mutex_lock l(mu);
- status_.Update(s);
- note.Notify();
- });
- note.WaitForNotification();
- }
+ int num_subdivs = static_cast<int>(col_params_.subdiv_rank.size());
+ // TODO(ayushd): this is easily improved when a node participates in both
+ // first and second subdivision. It would first send to its descendents in
+ // the first subdiv, then wait until all pending ops are finished before
+ // sending to descendents in second subdiv. A better implementation would
+ // collapse the two send blocks.
+ for (int si = 0; si < num_subdivs; si++) {
+ int my_rank = col_params_.subdiv_rank[si];
+ // If rank is -1, this device does not participate in this subdiv.
+ if (-1 == my_rank) continue;
+ int source_rank = col_params_.instance.impl_details.subdiv_source_rank[si];
+ if (VLOG_IS_ON(1)) {
+ string subdiv_buf;
+ for (int r : col_params_.instance.impl_details.subdiv_permutations[si]) {
+ strings::StrAppend(&subdiv_buf, r, ",");
+ }
+ VLOG(1) << "Running Broadcast tree device=" << device_->name()
+ << " subdiv=" << si << " perm=" << subdiv_buf
+ << " my_rank=" << my_rank << " source_rank=" << source_rank;
+ }
+
+ mutex mu; // also guards status_ while callbacks are pending
+ int pending_count = 0; // GUARDED_BY(mu)
+ condition_variable all_done;
- // Then forward value to all descendent devices.
- if (status_.ok()) {
- for (int i = 0; i < send_to_ranks.size(); ++i) {
- int target_rank = send_to_ranks[i];
- {
- mutex_lock l(mu);
- ++pending_count;
+ if (my_rank >= 0 && my_rank != source_rank) {
+ // Begin by receiving the value.
+ int recv_from_rank = TreeRecvFrom(col_params_, si);
+ Notification note;
+ DispatchRecv(si, recv_from_rank, my_rank, output_,
+ [this, &mu, &note](const Status& s) {
+ mutex_lock l(mu);
+ status_.Update(s);
+ note.Notify();
+ });
+ note.WaitForNotification();
+ }
+
+ // Then forward value to all descendent devices.
+ if (my_rank >= 0 && status_.ok()) {
+ std::vector<int> send_to_ranks;
+ TreeSendTo(col_params_, si, &send_to_ranks);
+ for (int i = 0; i < send_to_ranks.size(); ++i) {
+ int target_rank = send_to_ranks[i];
+ {
+ mutex_lock l(mu);
+ ++pending_count;
+ }
+ DispatchSend(si, target_rank, my_rank,
+ (is_source_ ? &ctx_->input(0) : output_),
+ [this, &mu, &pending_count, &all_done](const Status& s) {
+ mutex_lock l(mu);
+ status_.Update(s);
+ --pending_count;
+ if (pending_count == 0) {
+ all_done.notify_all();
+ }
+ });
}
- DispatchSend(
- target_rank, (is_source_ ? &ctx_->input(0) : output_),
- [this, target_rank, &mu, &pending_count, &all_done](const Status& s) {
- mutex_lock l(mu);
- status_.Update(s);
- --pending_count;
- if (pending_count == 0) {
- all_done.notify_all();
- }
- });
}
- }
- if (status_.ok() && is_source_) {
- // Meanwhile, copy input to output if we weren't lucky enough to
- // be able to reuse input as output.
- const Tensor* input = &ctx_->input(0);
- if (input != output_ &&
- (DMAHelper::base(input) != DMAHelper::base(output_))) {
- {
- mutex_lock l(mu);
- ++pending_count;
+ // For the original source device, we copy input to output if they are
+ // different.
+ // If there is only 1 subdiv, we do this in that subdiv. If there is more
+ // than 1 subdiv, then the original source device will participate in 2
+ // subdivs - the global inter-task broadcast and one local intra-task
+ // broadcast. In this case, we perform the copy in the second subdiv for
+ // this device.
+ if (status_.ok() && is_source_ && (1 == num_subdivs || 0 != si)) {
+ VLOG(2) << "copying input to output for device=" << device_->name()
+ << " subdiv=" << si;
+ const Tensor* input = &ctx_->input(0);
+ if (input != output_ &&
+ (DMAHelper::base(input) != DMAHelper::base(output_))) {
+ {
+ mutex_lock l(mu);
+ ++pending_count;
+ }
+ DeviceContext* op_dev_ctx = ctx_->op_device_context();
+ CollectiveRemoteAccessLocal::MemCpyAsync(
+ op_dev_ctx, op_dev_ctx, device_, device_, ctx_->input_alloc_attr(0),
+ ctx_->output_alloc_attr(0), input, output_, 0, /*stream_index*/
+ [this, &mu, &pending_count, &all_done](const Status& s) {
+ mutex_lock l(mu);
+ status_.Update(s);
+ --pending_count;
+ if (0 == pending_count) {
+ all_done.notify_all();
+ }
+ });
}
- DeviceContext* op_dev_ctx = ctx_->op_device_context();
- CollectiveRemoteAccessLocal::MemCpyAsync(
- op_dev_ctx, op_dev_ctx, device_, device_, ctx_->input_alloc_attr(0),
- ctx_->output_alloc_attr(0), input, output_, 0 /*steam_index*/,
- [this, &mu, &pending_count, &all_done](const Status& s) {
- mutex_lock l(mu);
- status_.Update(s);
- --pending_count;
- if (0 == pending_count) {
- all_done.notify_all();
- }
- });
}
- }
- // Then wait for all pending actions to complete.
- {
- mutex_lock l(mu);
- if (pending_count > 0) {
- all_done.wait(l);
+ // Then wait for all pending actions to complete.
+ {
+ mutex_lock l(mu);
+ if (pending_count > 0) {
+ all_done.wait(l);
+ }
}
}
-
- VLOG(2) << "return status " << status_;
+ VLOG(2) << "device=" << device_->name() << " return status " << status_;
done_(status_);
}
-void Broadcaster::DispatchSend(int dst_rank, const Tensor* src_tensor,
+void Broadcaster::DispatchSend(int subdiv, int dst_rank, int src_rank,
+ const Tensor* src_tensor,
const StatusCallback& done) {
- string send_buf_key = BroadcastBufKey(exec_key_, rank_, dst_rank);
- VLOG(1) << "DispatchSend " << send_buf_key << " from_device "
- << device_->name();
+ string send_buf_key = BroadcastBufKey(exec_key_, subdiv, src_rank, dst_rank);
int dst_idx =
- col_params_.instance.impl_details.subdiv_permutations[0][dst_rank];
+ col_params_.instance.impl_details.subdiv_permutations[subdiv][dst_rank];
+ VLOG(1) << "DispatchSend " << send_buf_key << " from_device "
+ << device_->name() << " to_device "
+ << col_params_.instance.device_names[dst_idx] << " subdiv=" << subdiv
+ << " dst_rank=" << dst_rank << " dst_idx=" << dst_idx;
col_exec_->PostToPeer(col_params_.instance.device_names[dst_idx],
col_params_.instance.task_names[dst_idx], send_buf_key,
device_, ctx_->op_device_context(),
@@ -225,15 +280,15 @@ void Broadcaster::DispatchSend(int dst_rank, const Tensor* src_tensor,
device_locality_, done);
}
-void Broadcaster::DispatchRecv(int src_rank, Tensor* dst_tensor,
- const StatusCallback& done) {
- string recv_buf_key = BroadcastBufKey(exec_key_, src_rank, rank_);
+void Broadcaster::DispatchRecv(int subdiv, int src_rank, int dst_rank,
+ Tensor* dst_tensor, const StatusCallback& done) {
+ string recv_buf_key = BroadcastBufKey(exec_key_, subdiv, src_rank, dst_rank);
int src_idx =
- col_params_.instance.impl_details.subdiv_permutations[0][src_rank];
+ col_params_.instance.impl_details.subdiv_permutations[subdiv][src_rank];
VLOG(1) << "DispatchRecv " << recv_buf_key << " from_device "
- << col_params_.instance.device_names[src_idx];
- int dst_idx = col_params_.instance.impl_details.subdiv_permutations[0][rank_];
- CHECK_EQ(col_params_.instance.device_names[dst_idx], device_->name());
+ << col_params_.instance.device_names[src_idx] << " to_device "
+ << device_->name() << " subdiv=" << subdiv << " src_rank=" << src_rank
+ << " src_idx=" << src_idx;
col_exec_->RecvFromPeer(col_params_.instance.device_names[src_idx],
col_params_.instance.task_names[src_idx],
col_params_.task.is_local[src_idx], recv_buf_key,
diff --git a/tensorflow/core/common_runtime/broadcaster.h b/tensorflow/core/common_runtime/broadcaster.h
index bdf68f19ab..799228b161 100644
--- a/tensorflow/core/common_runtime/broadcaster.h
+++ b/tensorflow/core/common_runtime/broadcaster.h
@@ -34,17 +34,24 @@ class Broadcaster {
// Returns the rank of the device from which this device should receive
// its value, -1 if no value should be received.
- static int TreeRecvFrom(const CollectiveParams& cp);
+ static int TreeRecvFrom(const CollectiveParams& cp, int subdiv);
// Populates targets with the ranks of the devices to which this device
// should forward the value.
- static void TreeSendTo(const CollectiveParams& cp, std::vector<int>* targets);
+ static void TreeSendTo(const CollectiveParams& cp, int subdiv,
+ std::vector<int>* targets);
private:
- void DispatchSend(int dst_rank, const Tensor* src_tensor,
- const StatusCallback& done);
- void DispatchRecv(int src_rank, Tensor* dst_tensor,
+ // Sends `src_tensor` asynchronously from this device to device at `dst_rank`
+ // in `subdiv`. Calls `done` upon completion.
+ void DispatchSend(int subdiv, int dst_rank, int src_rank,
+ const Tensor* src_tensor, const StatusCallback& done);
+ // Receives a tensor into the memory buffer owned by `dst_tensor` at this
+ // device from device at `src_rank` in `subdiv`. Calls `done` upon
+ // completion.
+ void DispatchRecv(int subdiv, int src_rank, int dst_rank, Tensor* dst_tensor,
const StatusCallback& done);
+ // Executes the hierarchical broadcast defined by this op.
void RunTree();
Status status_;
diff --git a/tensorflow/core/common_runtime/broadcaster_test.cc b/tensorflow/core/common_runtime/broadcaster_test.cc
index 6a163a0db0..3960fc6c97 100644
--- a/tensorflow/core/common_runtime/broadcaster_test.cc
+++ b/tensorflow/core/common_runtime/broadcaster_test.cc
@@ -38,7 +38,6 @@ namespace tensorflow {
namespace {
static int64 kStepId = 123;
-static int32 kNumSubdivs = 1; // Subdiv not yet meaningful for broadcast
// The test harness won't allow a mixture of fixture and non-fixture
// tests in one file, so this is a trival fixture for tests that don't
@@ -59,12 +58,14 @@ class TrivialTest : public ::testing::Test {
CollectiveParams cp; \
cp.group.group_size = D; \
cp.instance.impl_details.subdiv_source_rank = {S}; \
+ cp.instance.impl_details.subdiv_permutations.push_back( \
+ std::vector<int>(D, 0)); \
cp.subdiv_rank = {R}; \
cp.is_source = (S == R); \
- EXPECT_EQ(RF, Broadcaster::TreeRecvFrom(cp)); \
+ EXPECT_EQ(RF, Broadcaster::TreeRecvFrom(cp, 0)); \
std::vector<int> expected = ST; \
std::vector<int> send_to; \
- Broadcaster::TreeSendTo(cp, &send_to); \
+ Broadcaster::TreeSendTo(cp, 0, &send_to); \
ASSERT_EQ(expected.size(), send_to.size()); \
for (int i = 0; i < expected.size(); ++i) { \
EXPECT_EQ(expected[i], send_to[i]); \
@@ -209,8 +210,11 @@ class BroadcasterTest : public ::testing::Test {
#endif
}
- void Init(int num_workers, int num_devices, DataType dtype,
+ void Init(int num_workers, int num_devices_per_worker, DataType dtype,
const DeviceType& device_type, int fail_after) {
+ VLOG(2) << "num_workers=" << num_workers
+ << " num_devices_per_worker=" << num_devices_per_worker;
+ int total_num_devices = num_workers * num_devices_per_worker;
device_type_ = device_type;
std::vector<Device*> local_devices;
SessionOptions sess_opts;
@@ -218,14 +222,14 @@ class BroadcasterTest : public ::testing::Test {
Bytes mem_limit(4 << 20);
DeviceLocality dev_locality;
for (int wi = 0; wi < num_workers; ++wi) {
- for (int di = 0; di < num_devices; ++di) {
+ for (int di = 0; di < num_devices_per_worker; ++di) {
if (device_type == DEVICE_CPU) {
string dev_name = strings::StrCat("/job:worker/replica:0/task:", wi,
"/device:CPU:", di);
local_devices.push_back(new ThreadPoolDevice(
sess_opts, dev_name, mem_limit, dev_locality, cpu_allocator()));
} else if (device_type == DEVICE_GPU && !gpu_devices_.empty()) {
- int dev_idx = (wi * num_devices) + di;
+ int dev_idx = (wi * num_devices_per_worker) + di;
if (dev_idx >= static_cast<int>(gpu_devices_.size())) {
LOG(INFO) << "dev_mgr has access to limited GPUs, reusing for more "
"than one ring node.";
@@ -247,67 +251,86 @@ class BroadcasterTest : public ::testing::Test {
dev_mgr_.get());
col_params_.name = "test_collective";
col_params_.instance.data_type = dtype;
- static const int kGroupKey = 5;
+ static const int kGroupKey = 6;
col_params_.group.group_key = kGroupKey;
- static const int kInstanceKey = 17;
+ static const int kInstanceKey = 18;
col_params_.instance.instance_key = kInstanceKey;
col_params_.group.device_type = device_type;
- col_params_.group.group_size = num_workers * num_devices;
+ col_params_.group.group_size = num_workers * num_devices_per_worker;
col_params_.instance.impl_details.subdiv_offsets.clear();
col_params_.instance.type = BROADCAST_COLLECTIVE;
- col_params_.instance.impl_details.subdiv_permutations.resize(kNumSubdivs);
- col_params_.subdiv_rank.resize(kNumSubdivs);
- int subdiv_stride = num_devices / kNumSubdivs;
- for (int sdi = 0; sdi < kNumSubdivs; ++sdi) {
- col_params_.instance.impl_details.subdiv_offsets.push_back(sdi *
- subdiv_stride);
- col_params_.subdiv_rank[sdi] = sdi * subdiv_stride;
- }
- // Set up a local device ring order that's not just 0,1,2...
- std::vector<int> local_ring_order;
- for (int di = 0; di < num_devices; ++di) {
- local_ring_order.push_back(di);
+ int num_subdivs = num_workers + (num_workers > 1 ? 1 : 0);
+ VLOG(2) << "#subdiv=" << num_subdivs;
+ col_params_.instance.impl_details.subdiv_permutations.resize(num_subdivs);
+ col_params_.subdiv_rank.resize(num_subdivs);
+
+ // Inter-machine broadcast.
+ int subdiv_i = 0;
+ if (num_workers > 1) {
+ col_params_.instance.impl_details.subdiv_permutations[subdiv_i].resize(
+ total_num_devices, -1);
+ for (int i = 0, rank = 0; i < total_num_devices; i++) {
+ if (i % num_devices_per_worker == 0) {
+ col_params_.instance.impl_details
+ .subdiv_permutations[subdiv_i][rank] = i;
+ rank++;
+ }
+ }
+ if (VLOG_IS_ON(2)) {
+ string sp_buf;
+ for (int p :
+ col_params_.instance.impl_details.subdiv_permutations[subdiv_i])
+ strings::StrAppend(&sp_buf, p, ", ");
+ VLOG(2) << "subdiv_i=" << subdiv_i << " perm=" << sp_buf;
+ }
+ subdiv_i++;
}
- for (int di = 0; di < num_devices; ++di) {
- bool is_odd = ((di % 2) == 1);
- int other = (di + (is_odd ? 7 : 3)) % num_devices;
- if (di == other) continue;
- iter_swap(local_ring_order.begin() + di,
- local_ring_order.begin() + other);
+ // Intra-machine broadcast.
+ for (int i = 0; subdiv_i < num_subdivs; i++, subdiv_i++) {
+ col_params_.instance.impl_details.subdiv_permutations[subdiv_i].resize(
+ total_num_devices, -1);
+ int perm_i_base = i * num_devices_per_worker;
+ VLOG(2) << "subdiv_i=" << subdiv_i << " i=" << i
+ << " perm_i_base=" << perm_i_base << " subdiv_perms.size="
+ << col_params_.instance.impl_details.subdiv_permutations.size();
+ // subdiv for worker i.
+ for (int j = perm_i_base, rank = 0;
+ j < perm_i_base + num_devices_per_worker; j++, rank++) {
+ col_params_.instance.impl_details.subdiv_permutations[subdiv_i][rank] =
+ j;
+ }
+ if (VLOG_IS_ON(2)) {
+ string sp_buf;
+ for (int p :
+ col_params_.instance.impl_details.subdiv_permutations[subdiv_i])
+ strings::StrAppend(&sp_buf, p, ", ");
+ VLOG(2) << "subdiv_i=" << subdiv_i << " perm=" << sp_buf;
+ }
}
- broadcast_dev_id_ = local_ring_order[0];
- string lro_buf;
- for (auto d : local_ring_order) strings::StrAppend(&lro_buf, d, ", ");
- VLOG(1) << "local_ring_order " << lro_buf;
- // Set up all of the fake device contexts.
- for (int wi = 0; wi < num_workers; ++wi) {
- for (int di = 0; di < num_devices; ++di) {
+ // Set up all the fake device contexts.
+ for (int wi = 0; wi < num_workers; wi++) {
+ for (int di = 0; di < num_devices_per_worker; di++) {
string task_name = strings::StrCat("/job:worker/replica:0/task:", wi);
- string dev_name = strings::StrCat(task_name, "/device:CPU:", di);
+ string dev_name;
if (device_type == DEVICE_GPU) {
dev_name = strings::StrCat(task_name, "/device:GPU:0");
+ } else {
+ dev_name = strings::StrCat(task_name, "/device:CPU:", di);
}
+ VLOG(2) << "dev=" << dev_name;
col_params_.instance.device_names.push_back(dev_name);
col_params_.instance.task_names.push_back(task_name);
- // Normally each device would set is_local to its own perspective but
- // this test runs in a single process so is_local is always true.
col_params_.task.is_local.push_back(true);
- for (int sdi = 0; sdi < kNumSubdivs; ++sdi) {
- int rotated_di =
- (di + col_params_.instance.impl_details.subdiv_offsets[sdi]) %
- num_devices;
- col_params_.instance.impl_details.subdiv_permutations[sdi].push_back(
- wi * num_devices + local_ring_order[rotated_di]);
- }
}
}
- for (int wi = 0; wi < num_workers; ++wi) {
- for (int di = 0; di < num_devices; ++di) {
- int rank = wi * num_devices + di;
+ for (int wi = 0; wi < num_workers; wi++) {
+ for (int di = 0; di < num_devices_per_worker; di++) {
+ int default_rank = wi * num_devices_per_worker + di;
instances_.push_back(new DeviceInstance(
- rank, col_params_.instance.device_names[rank], device_type_, this));
+ default_rank, col_params_.instance.device_names[default_rank],
+ device_type, this));
}
}
}
@@ -315,6 +338,7 @@ class BroadcasterTest : public ::testing::Test {
typedef std::function<void(Tensor*)> InitFunc;
void Broadcast(bool forward_input) {
+ VLOG(2) << "#instances=" << instances_.size();
std::atomic<int> done(0);
for (auto di : instances_) {
SchedClosure([di, forward_input, &done] {
@@ -516,39 +540,29 @@ class BroadcasterTest : public ::testing::Test {
CHECK_EQ(group_size, col_params_.instance.device_names.size());
// Default rank is order in device_names.
col_params_.default_rank = rank;
- // perm_rank is order in subdiv[0]:
- int perm_rank = -1;
- for (int i = 0;
- i < col_params_.instance.impl_details.subdiv_permutations[0].size();
- ++i) {
- if (rank ==
- col_params_.instance.impl_details.subdiv_permutations[0][i]) {
- perm_rank = i;
- break;
- }
- }
- CHECK_GE(perm_rank, 0);
- col_params_.instance.impl_details.subdiv_source_rank.resize(1, 0);
- col_params_.is_source =
- (perm_rank ==
- col_params_.instance.impl_details.subdiv_source_rank[0]);
- // Set rank in all subdivs by finding that default_rank.
- for (int sdi = 0; sdi < kNumSubdivs; ++sdi) {
- for (int r = 0;
- r <
- col_params_.instance.impl_details.subdiv_permutations[sdi].size();
- ++r) {
- if (col_params_.default_rank ==
- col_params_.instance.impl_details.subdiv_permutations[sdi][r]) {
- col_params_.subdiv_rank[sdi] = r;
- CHECK_EQ(0, sdi);
- CHECK_EQ(perm_rank, col_params_.subdiv_rank[sdi]);
+
+ auto& impl = col_params_.instance.impl_details;
+ size_t num_subdivs = impl.subdiv_permutations.size();
+ impl.subdiv_source_rank.resize(num_subdivs, 0);
+ col_params_.subdiv_rank.resize(num_subdivs);
+ for (size_t si = 0; si < num_subdivs; si++) {
+ int perm_rank = -1;
+ for (int i = 0; i < group_size; i++) {
+ if (rank == impl.subdiv_permutations[si][i]) {
+ perm_rank = i;
break;
}
}
+ col_params_.subdiv_rank[si] = perm_rank;
+ }
+ string rank_buf;
+ for (int r : col_params_.subdiv_rank) {
+ strings::StrAppend(&rank_buf, r, ", ");
}
- CHECK_EQ(group_size, col_params_.task.is_local.size());
- CHECK_EQ(group_size, col_params_.instance.task_names.size());
+ VLOG(1) << "default=" << rank << " subdiv_ranks=" << rank_buf;
+
+ col_params_.is_source =
+ col_params_.subdiv_rank[0] == impl.subdiv_source_rank[0];
}
void InitTensor(DataType dtype, const TensorShape& shape,
diff --git a/tensorflow/core/common_runtime/collective_param_resolver_local.cc b/tensorflow/core/common_runtime/collective_param_resolver_local.cc
index 236f999228..2a14493a67 100644
--- a/tensorflow/core/common_runtime/collective_param_resolver_local.cc
+++ b/tensorflow/core/common_runtime/collective_param_resolver_local.cc
@@ -319,6 +319,97 @@ void SortDevicesAndTasks(CollectiveParams* cp) {
}
} // namespace
+int GetDeviceTask(int device_rank, const std::vector<int>& dev_per_task) {
+ int num_tasks = static_cast<int>(dev_per_task.size());
+ int task_lo = 0;
+ int task_hi;
+ for (int ti = 0; ti < num_tasks; ti++) {
+ task_hi = task_lo + dev_per_task[ti];
+ if (task_lo <= device_rank && device_rank < task_hi) return ti;
+ task_lo += dev_per_task[ti];
+ }
+ LOG(FATAL) << "Unexpected device rank " << device_rank << " for " << task_hi
+ << " devices";
+ return -1;
+}
+
+void CollectiveParamResolverLocal::GenerateBcastSubdivPerms(
+ const string& device, int source_rank, const std::vector<int>& dev_per_task,
+ CollectiveParams* cp) {
+ if (VLOG_IS_ON(1)) {
+ string dpt_buf;
+ for (int dpt : dev_per_task) strings::StrAppend(&dpt_buf, dpt, ";");
+ VLOG(1) << "GenerateBcastSubdivPerms device=" << device
+ << " source_rank=" << source_rank << " dev_per_task=" << dpt_buf;
+ }
+ int num_tasks = cp->group.num_tasks;
+ // If there is just 1 task, then execute binary tree broadcast over all
+ // devices. Otherwise, the first subdiv is inter-task broadcast, and then
+ // there are N more subdivs, where N is #task.
+ int num_subdivs = num_tasks + (num_tasks > 1 ? 1 : 0);
+ int total_num_devices = 0;
+ for (int num_dev : dev_per_task) total_num_devices += num_dev;
+
+ cp->instance.impl_details.subdiv_permutations.resize(num_subdivs);
+ cp->subdiv_rank.reserve(num_subdivs);
+ cp->instance.impl_details.subdiv_source_rank.reserve(num_subdivs);
+
+ // Inter-task subdiv. Pick one device from each task - this is the source
+ // device if it belongs to that task, or device 0 for that task. If a device
+ // does not participate in the subdiv, set subdiv_rank to -1.
+ if (num_tasks > 1) {
+ const int sdi = 0;
+ std::vector<int>& perm = cp->instance.impl_details.subdiv_permutations[sdi];
+ CHECK_EQ(perm.size(), 0);
+ int device_count = 0;
+ int source_task = GetDeviceTask(source_rank, dev_per_task);
+ for (int ti = 0; ti < cp->group.num_tasks; ti++) {
+ bool participate = false;
+ if (source_task == ti) {
+ // Source device belongs to this task.
+ perm.push_back(source_rank);
+ participate = cp->instance.device_names[source_rank] == device;
+ } else {
+ // Source does not belong to this task, choose dev 0.
+ perm.push_back(device_count);
+ participate = cp->instance.device_names[device_count] == device;
+ }
+ if (participate) cp->subdiv_rank.push_back(ti);
+ device_count += dev_per_task[ti];
+ }
+ if (cp->subdiv_rank.empty()) cp->subdiv_rank.push_back(-1);
+ cp->instance.impl_details.subdiv_source_rank.push_back(source_task);
+ }
+
+ // Intra-task subdivs. Pick all devices in task ti for subdiv sdi. Set
+ // source to dev 0 for that task if it does not contain original source, else
+ // set to rank of original source. If a device does not participate in the
+ // subdiv, set subdiv_rank to -1;
+ int abs_di = 0;
+ for (int ti = 0; ti < cp->group.num_tasks; ti++) {
+ const int sdi = ti + (num_tasks > 1 ? 1 : 0);
+ std::vector<int>& perm = cp->instance.impl_details.subdiv_permutations[sdi];
+ CHECK_EQ(perm.size(), 0);
+ bool participate = false;
+ int subdiv_source = 0;
+ for (int di = 0; di < dev_per_task[ti]; di++) {
+ perm.push_back(abs_di);
+ if (cp->instance.device_names[abs_di] == device) {
+ participate = true;
+ cp->subdiv_rank.push_back(di);
+ }
+ if (abs_di == source_rank) subdiv_source = di;
+ abs_di++;
+ }
+ if (!participate) cp->subdiv_rank.push_back(-1);
+ cp->instance.impl_details.subdiv_source_rank.push_back(subdiv_source);
+ }
+
+ for (int sri = 0; sri < num_subdivs; sri++) {
+ CHECK_GE(cp->instance.impl_details.subdiv_source_rank[sri], 0);
+ }
+}
+
// Establish the requested number of subdivision permutations based on the
// ring order implicit in the device order.
/*static*/
@@ -351,61 +442,51 @@ void CollectiveParamResolverLocal::GenerateSubdivPerms(const string& device,
dev_per_task.push_back(dev_count);
CHECK_EQ(cp->group.num_tasks, dev_per_task.size());
- // Generate a ring permutation for each requested offset.
- CHECK_GT(cp->instance.impl_details.subdiv_offsets.size(), 0);
- VLOG(2) << "Setting up perms for cp " << cp << " subdiv_permutations "
- << &cp->instance.impl_details.subdiv_permutations;
- cp->instance.impl_details.subdiv_permutations.resize(
- cp->instance.impl_details.subdiv_offsets.size());
- cp->subdiv_rank.resize(cp->instance.impl_details.subdiv_offsets.size(), -1);
- for (int sdi = 0; sdi < cp->instance.impl_details.subdiv_offsets.size();
- ++sdi) {
- std::vector<int>& perm = cp->instance.impl_details.subdiv_permutations[sdi];
- CHECK_EQ(perm.size(), 0);
- int offset = cp->instance.impl_details.subdiv_offsets[sdi];
- // A negative subdivision offset is interpreted as follows:
- // 1. Reverse the local device ordering.
- // 2. Begin the subdivision at abs(offset) in the reversed ordering.
- bool reverse = false;
- if (offset < 0) {
- offset = abs(offset);
- reverse = true;
- }
- int prior_dev_count = 0; // sum over prior worker device counts
- for (int ti = 0; ti < cp->group.num_tasks; ++ti) {
- for (int di = 0; di < dev_per_task[ti]; ++di) {
- int di_offset = (di + offset) % dev_per_task[ti];
- int offset_di =
- reverse ? (dev_per_task[ti] - (di_offset + 1)) : di_offset;
- // Device index in global subdivision permutation.
- int permuted_di = prior_dev_count + offset_di;
- int rank = static_cast<int>(perm.size());
- perm.push_back(permuted_di);
- if (cp->instance.device_names[permuted_di] == device) {
- CHECK_EQ(permuted_di, cp->default_rank);
- cp->subdiv_rank[sdi] = rank;
- }
- }
- prior_dev_count += dev_per_task[ti];
- }
- CHECK_EQ(cp->group.group_size, perm.size());
- }
-
- if (cp->instance.type == BROADCAST_COLLECTIVE) {
- CHECK_GE(source_rank, 0);
- cp->instance.impl_details.subdiv_source_rank.resize(
- cp->instance.impl_details.subdiv_offsets.size(), -1);
- for (int sdi = 0; sdi < cp->instance.impl_details.subdiv_source_rank.size();
+ CHECK(cp->instance.type == REDUCTION_COLLECTIVE ||
+ cp->instance.type == BROADCAST_COLLECTIVE);
+ if (cp->instance.type == REDUCTION_COLLECTIVE) {
+ // Generate a ring permutation for each requested offset.
+ CHECK_GT(cp->instance.impl_details.subdiv_offsets.size(), 0);
+ VLOG(2) << "Setting up perms for cp " << cp << " subdiv_permutations "
+ << &cp->instance.impl_details.subdiv_permutations;
+ cp->instance.impl_details.subdiv_permutations.resize(
+ cp->instance.impl_details.subdiv_offsets.size());
+ cp->subdiv_rank.resize(cp->instance.impl_details.subdiv_offsets.size(), -1);
+ for (int sdi = 0; sdi < cp->instance.impl_details.subdiv_offsets.size();
++sdi) {
- for (int j = 0; j < cp->group.group_size; ++j) {
- if (cp->instance.impl_details.subdiv_permutations[sdi][j] ==
- source_rank) {
- cp->instance.impl_details.subdiv_source_rank[sdi] = j;
- break;
+ std::vector<int>& perm =
+ cp->instance.impl_details.subdiv_permutations[sdi];
+ CHECK_EQ(perm.size(), 0);
+ int offset = cp->instance.impl_details.subdiv_offsets[sdi];
+ // A negative subdivision offset is interpreted as follows:
+ // 1. Reverse the local device ordering.
+ // 2. Begin the subdivision at abs(offset) in the reversed ordering.
+ bool reverse = false;
+ if (offset < 0) {
+ offset = abs(offset);
+ reverse = true;
+ }
+ int prior_dev_count = 0; // sum over prior worker device counts
+ for (int ti = 0; ti < cp->group.num_tasks; ++ti) {
+ for (int di = 0; di < dev_per_task[ti]; ++di) {
+ int di_offset = (di + offset) % dev_per_task[ti];
+ int offset_di =
+ reverse ? (dev_per_task[ti] - (di_offset + 1)) : di_offset;
+ // Device index in global subdivision permutation.
+ int permuted_di = prior_dev_count + offset_di;
+ int rank = static_cast<int>(perm.size());
+ perm.push_back(permuted_di);
+ if (cp->instance.device_names[permuted_di] == device) {
+ CHECK_EQ(permuted_di, cp->default_rank);
+ cp->subdiv_rank[sdi] = rank;
+ }
}
+ prior_dev_count += dev_per_task[ti];
}
- CHECK_GE(cp->instance.impl_details.subdiv_source_rank[sdi], 0);
+ CHECK_EQ(cp->group.group_size, perm.size());
}
+ } else if (cp->instance.type == BROADCAST_COLLECTIVE) {
+ GenerateBcastSubdivPerms(device, source_rank, dev_per_task, cp);
}
if (VLOG_IS_ON(1)) {
@@ -418,13 +499,21 @@ void CollectiveParamResolverLocal::GenerateSubdivPerms(const string& device,
di < cp->instance.impl_details.subdiv_permutations[sdi].size();
++di) {
int idx = cp->instance.impl_details.subdiv_permutations[sdi][di];
- strings::StrAppend(&buf, cp->instance.device_names[idx], "\n");
+ if (idx >= 0) {
+ CHECK_GT(cp->instance.device_names.size(), idx);
+ strings::StrAppend(&buf, cp->instance.device_names[idx], "\n");
+ }
}
strings::StrAppend(&buf, " subdiv_offsets: ");
for (auto o : cp->instance.impl_details.subdiv_offsets)
strings::StrAppend(&buf, o, " ");
strings::StrAppend(&buf, " SubdivRank: ");
for (auto d : cp->subdiv_rank) strings::StrAppend(&buf, d, " ");
+ if (cp->instance.type == BROADCAST_COLLECTIVE) {
+ strings::StrAppend(&buf, " subdiv_source_rank: ");
+ for (auto src : cp->instance.impl_details.subdiv_source_rank)
+ strings::StrAppend(&buf, src, " ");
+ }
VLOG(1) << buf;
}
}
diff --git a/tensorflow/core/common_runtime/collective_param_resolver_local.h b/tensorflow/core/common_runtime/collective_param_resolver_local.h
index 01bdeca7d1..2e2aa801d9 100644
--- a/tensorflow/core/common_runtime/collective_param_resolver_local.h
+++ b/tensorflow/core/common_runtime/collective_param_resolver_local.h
@@ -213,8 +213,16 @@ class CollectiveParamResolverLocal : public ParamResolverInterface {
LOCKS_EXCLUDED(irec->out_mu);
friend class CollectiveParamResolverLocalTest;
+ // Establishes the requested number of subdivision permutations based on the
+ // ring order implicit in the device order.
static void GenerateSubdivPerms(const string& device, int source_rank,
CollectiveParams* cp);
+ // Establishes the subdivisions for broadcast op. The first subdiv executes
+ // binary tree bcast with one device per task. Each subsequent subdiv
+ // executes intra-task binary tree broadcast.
+ static void GenerateBcastSubdivPerms(const string& device, int source_rank,
+ const std::vector<int>& dev_per_task,
+ CollectiveParams* cp);
const DeviceMgr* dev_mgr_;
DeviceResolverInterface* dev_resolver_; // Not owned.
diff --git a/tensorflow/core/common_runtime/collective_param_resolver_local_test.cc b/tensorflow/core/common_runtime/collective_param_resolver_local_test.cc
index d5be8f927e..9ea23b72d2 100644
--- a/tensorflow/core/common_runtime/collective_param_resolver_local_test.cc
+++ b/tensorflow/core/common_runtime/collective_param_resolver_local_test.cc
@@ -49,6 +49,26 @@ class CollectiveParamResolverLocalTest : public ::testing::Test {
CollectiveParamResolverLocal::GenerateSubdivPerms(device, source_rank, cp);
}
+ // Calls GenerateBcastSubdivPerms for device at `device_rank`. Checks if the
+ // generated subdiv perms, ranks, and source ranks match the expected values.
+ void BcastSubdivPerms(
+ CollectiveParams* cp, const std::vector<int>& dev_per_task,
+ int device_rank, int source_rank,
+ const std::vector<std::vector<int>>& expected_subdiv_perms,
+ const std::vector<int>& expected_subdiv_rank,
+ const std::vector<int>& expected_subdiv_source_rank) {
+ cp->subdiv_rank.clear();
+ cp->instance.impl_details.subdiv_permutations.clear();
+ cp->instance.impl_details.subdiv_source_rank.clear();
+ CollectiveParamResolverLocal::GenerateBcastSubdivPerms(
+ cp->instance.device_names[device_rank], source_rank, dev_per_task, cp);
+ EXPECT_EQ(expected_subdiv_perms,
+ cp->instance.impl_details.subdiv_permutations);
+ EXPECT_EQ(expected_subdiv_rank, cp->subdiv_rank);
+ EXPECT_EQ(expected_subdiv_source_rank,
+ cp->instance.impl_details.subdiv_source_rank);
+ }
+
std::vector<Device*> devices_;
std::unique_ptr<DeviceMgr> device_mgr_;
std::unique_ptr<DeviceResolverLocal> drl_;
@@ -216,4 +236,113 @@ TEST_F(CollectiveParamResolverLocalTest, GenerateSubdivPerms) {
EXPECT_EQ(1, cp.subdiv_rank[1]);
}
+TEST_F(CollectiveParamResolverLocalTest, GenerateBcastSubdivPerms1Task8GPU) {
+ CollectiveParams cp;
+ cp.group.device_type = DeviceType("GPU");
+ cp.group.num_tasks = 1;
+ cp.instance.type = BROADCAST_COLLECTIVE;
+ for (int i = 0; i < 8; i++) {
+ string dev_name =
+ strings::StrCat("/job:worker/replica:0/task:0/device:GPU:", i);
+ cp.instance.device_names.push_back(dev_name);
+ }
+ std::vector<int> dev_per_task = {8};
+
+ // source 0 device 0
+ BcastSubdivPerms(&cp, dev_per_task, 0, 0, {{0, 1, 2, 3, 4, 5, 6, 7}}, {0},
+ {0});
+
+ // source 2 device 2
+ BcastSubdivPerms(&cp, dev_per_task, 2, 2, {{0, 1, 2, 3, 4, 5, 6, 7}}, {2},
+ {2});
+
+ // source 2 device 0
+ BcastSubdivPerms(&cp, dev_per_task, 0, 2, {{0, 1, 2, 3, 4, 5, 6, 7}}, {0},
+ {2});
+}
+
+TEST_F(CollectiveParamResolverLocalTest, GenerateBcastSubdivPerms4Tasks8GPU) {
+ CollectiveParams cp;
+ cp.group.device_type = DeviceType("GPU");
+ cp.group.num_tasks = 4;
+ cp.instance.type = BROADCAST_COLLECTIVE;
+ for (int ti = 0; ti < cp.group.num_tasks; ti++) {
+ for (int di = 0; di < 8; di++) {
+ string dev_name = strings::StrCat("/job:worker/replica:0/task:", ti,
+ "/device:GPU:", di);
+ cp.instance.device_names.push_back(dev_name);
+ }
+ }
+ std::vector<int> dev_per_task = {8, 8, 8, 8};
+
+ // source 0 device 0
+ BcastSubdivPerms(&cp, dev_per_task, 0, 0,
+ {{0, 8, 16, 24},
+ {0, 1, 2, 3, 4, 5, 6, 7},
+ {8, 9, 10, 11, 12, 13, 14, 15},
+ {16, 17, 18, 19, 20, 21, 22, 23},
+ {24, 25, 26, 27, 28, 29, 30, 31}},
+ {0, 0, -1, -1, -1}, {0, 0, 0, 0, 0});
+
+ // source 2 device 0
+ BcastSubdivPerms(&cp, dev_per_task, 0, 2,
+ {{2, 8, 16, 24},
+ {0, 1, 2, 3, 4, 5, 6, 7},
+ {8, 9, 10, 11, 12, 13, 14, 15},
+ {16, 17, 18, 19, 20, 21, 22, 23},
+ {24, 25, 26, 27, 28, 29, 30, 31}},
+ {-1, 0, -1, -1, -1}, {0, 2, 0, 0, 0});
+
+ // source 9 device 9
+ BcastSubdivPerms(&cp, dev_per_task, 9, 9,
+ {{0, 9, 16, 24},
+ {0, 1, 2, 3, 4, 5, 6, 7},
+ {8, 9, 10, 11, 12, 13, 14, 15},
+ {16, 17, 18, 19, 20, 21, 22, 23},
+ {24, 25, 26, 27, 28, 29, 30, 31}},
+ {1, -1, 1, -1, -1}, {1, 0, 1, 0, 0});
+}
+
+TEST_F(CollectiveParamResolverLocalTest,
+ GenerateBcastSubdivPerms4TasksVariableGPU) {
+ CollectiveParams cp;
+ cp.group.device_type = DeviceType("GPU");
+ cp.group.num_tasks = 4;
+ std::vector<int> dev_per_task = {4, 4, 6, 8};
+ for (int ti = 0; ti < cp.group.num_tasks; ti++) {
+ for (int di = 0; di < dev_per_task[ti]; di++) {
+ string dev_name = strings::StrCat("/job:worker/replica:0/task:", ti,
+ "/device:GPU:", di);
+ cp.instance.device_names.push_back(dev_name);
+ }
+ }
+
+ // source 0 device 0
+ BcastSubdivPerms(&cp, dev_per_task, 0, 0,
+ {{0, 4, 8, 14},
+ {0, 1, 2, 3},
+ {4, 5, 6, 7},
+ {8, 9, 10, 11, 12, 13},
+ {14, 15, 16, 17, 18, 19, 20, 21}},
+ {0, 0, -1, -1, -1}, {0, 0, 0, 0, 0});
+
+ // source 2 device 0
+ BcastSubdivPerms(&cp, dev_per_task, 0, 2,
+ {{2, 4, 8, 14},
+ {0, 1, 2, 3},
+ {4, 5, 6, 7},
+ {8, 9, 10, 11, 12, 13},
+ {14, 15, 16, 17, 18, 19, 20, 21}},
+ {-1, 0, -1, -1, -1}, {0, 2, 0, 0, 0});
+
+ // source 9 device 5
+ BcastSubdivPerms(&cp, dev_per_task, 5, 9,
+ {{0, 4, 9, 14},
+ {0, 1, 2, 3},
+ {4, 5, 6, 7},
+ {8, 9, 10, 11, 12, 13},
+ {14, 15, 16, 17, 18, 19, 20, 21}},
+ {-1, -1, 1, -1, -1}, {2, 0, 0, 1, 0});
+}
+
} // namespace tensorflow
diff --git a/tensorflow/core/common_runtime/direct_session.cc b/tensorflow/core/common_runtime/direct_session.cc
index d1fd930d25..0695278c0d 100644
--- a/tensorflow/core/common_runtime/direct_session.cc
+++ b/tensorflow/core/common_runtime/direct_session.cc
@@ -26,6 +26,7 @@ limitations under the License.
#include "tensorflow/core/common_runtime/device_factory.h"
#include "tensorflow/core/common_runtime/device_resolver_local.h"
#include "tensorflow/core/common_runtime/executor.h"
+#include "tensorflow/core/common_runtime/executor_factory.h"
#include "tensorflow/core/common_runtime/function.h"
#include "tensorflow/core/common_runtime/graph_optimizer.h"
#include "tensorflow/core/common_runtime/memory_types.h"
@@ -1223,10 +1224,9 @@ Status DirectSession::CreateExecutors(
item->graph = partition_graph.get();
item->executor = nullptr;
item->device = device;
- Executor* executor;
- TF_RETURN_IF_ERROR(
- NewLocalExecutor(params, std::move(partition_graph), &executor));
- item->executor.reset(executor);
+ auto executor_type = options_.config.experimental().executor_type();
+ TF_RETURN_IF_ERROR(NewExecutor(
+ executor_type, params, std::move(partition_graph), &item->executor));
}
// Cache the mapping from input/output names to graph elements to
diff --git a/tensorflow/core/common_runtime/eager/context.cc b/tensorflow/core/common_runtime/eager/context.cc
index 5e0f0a45f8..b623ed4421 100644
--- a/tensorflow/core/common_runtime/eager/context.cc
+++ b/tensorflow/core/common_runtime/eager/context.cc
@@ -217,7 +217,7 @@ Status EagerContext::FindDeviceByName(const string& name, Device** result) {
Status EagerContext::MaybeRegisterFunctionRemotely(const FunctionDef& fdef) {
if (remote_device_manager_ == nullptr) return Status::OK();
-
+#ifndef __ANDROID__
BlockingCounter blocking_counter(static_cast<int>(remote_contexts_.size()));
std::vector<eager::RegisterFunctionRequest> requests(remote_contexts_.size());
@@ -247,6 +247,7 @@ Status EagerContext::MaybeRegisterFunctionRemotely(const FunctionDef& fdef) {
for (int i = 0; i < remote_contexts_.size(); i++) {
TF_RETURN_IF_ERROR(statuses[i]);
}
+#endif
return Status::OK();
}
diff --git a/tensorflow/core/common_runtime/eager/context.h b/tensorflow/core/common_runtime/eager/context.h
index 4a180e074d..9c8c599452 100644
--- a/tensorflow/core/common_runtime/eager/context.h
+++ b/tensorflow/core/common_runtime/eager/context.h
@@ -183,11 +183,12 @@ class EagerContext {
Status GetClientAndContextID(Device* device, eager::EagerClient** client,
uint64* context_id);
+#endif
// If true, then tensors should be shipped across processes via the
// EagerService.SendTensor RPC. If false, _Send/_Recv ops should be used
// instead (which in-turn use WorkerService.RecvTensor RPCs.
bool UseSendTensorRPC() { return use_send_tensor_rpc_; }
-#endif
+
private:
void InitDeviceMapAndAsync();
Status MaybeRegisterFunctionRemotely(const FunctionDef& fdef);
@@ -254,9 +255,9 @@ class EagerContext {
const gtl::FlatMap<string, uint64> remote_contexts_;
gtl::FlatMap<Device*, std::pair<eager::EagerClient*, uint64>>
device_to_client_cache_;
+#endif
const bool use_send_tensor_rpc_;
-#endif
};
} // namespace tensorflow
diff --git a/tensorflow/core/common_runtime/eager/execute.cc b/tensorflow/core/common_runtime/eager/execute.cc
index 7ea78b63d9..333778ddf8 100644
--- a/tensorflow/core/common_runtime/eager/execute.cc
+++ b/tensorflow/core/common_runtime/eager/execute.cc
@@ -175,7 +175,7 @@ Status ValidateInputTypeAndPlacement(EagerContext* ctx, Device* op_device,
tensorflow::TensorHandle* handle = op->Inputs()[i];
if (handle->dtype != kernel->input_type(i)) {
return errors::InvalidArgument(
- "cannot compute ", op->Name(), " as input #", i,
+ "cannot compute ", op->Name(), " as input #", i, "(zero-based)",
" was expected to be a ", DataTypeString(kernel->input_type(i)),
" tensor but is a ", DataTypeString(handle->dtype), " tensor");
}
@@ -448,6 +448,14 @@ bool IsLocal(EagerContext* ctx, tensorflow::Device* d) {
return ctx->local_device_mgr()->LookupDevice(d->name(), &tmp).ok();
}
+bool OnSameTask(EagerContext* ctx, Device* first, Device* second) {
+ if (first == nullptr) first = ctx->HostCPU();
+ if (second == nullptr) second = ctx->HostCPU();
+ return first->parsed_name().job == second->parsed_name().job &&
+ first->parsed_name().replica == second->parsed_name().replica &&
+ first->parsed_name().task == second->parsed_name().task;
+}
+
Status EagerLocalExecute(EagerOperation* op,
gtl::InlinedVector<TensorHandle*, 2>* retvals,
int* num_retvals) {
@@ -585,6 +593,7 @@ Status EagerLocalExecute(EagerOperation* op,
return status;
}
+#ifndef __ANDROID__
std::function<void()> GetRemoteTensorDestructor(
EagerContext* ctx, eager::EagerClient* eager_client, uint64 context_id,
uint64 op_id, int output_num) {
@@ -615,6 +624,7 @@ std::function<void()> GetRemoteTensorDestructor(
return tensorflow::Status::OK();
};
}
+#endif
// When !ctx->UseSendTensorRPC(), then tensors are shipped between remote
// devices by the receiver invoking the WorkerService.RecvTensor RPC *on the
@@ -626,6 +636,10 @@ std::function<void()> GetRemoteTensorDestructor(
// *on the receiver*.
Status EagerRemoteSendTensor(EagerContext* ctx, TensorHandle* h,
Device* recv_device, TensorHandle** result) {
+#ifdef __ANDROID__
+ return errors::Unimplemented(
+ "Eager's remote execution is not available on Android devices.");
+#else
eager::EagerClient* eager_client;
uint64 context_id;
TF_RETURN_IF_ERROR(
@@ -664,6 +678,7 @@ Status EagerRemoteSendTensor(EagerContext* ctx, TensorHandle* h,
(*result)->SetRemoteShape(MakeUnique<TensorShape>(tensor->shape()));
return Status::OK();
+#endif
}
Status EagerRemoteExecute(EagerOperation* op, TensorHandle** retvals,
@@ -689,7 +704,11 @@ Status EagerRemoteExecute(EagerOperation* op, TensorHandle** retvals,
for (int i = 0; i < op->Inputs().size(); i++) {
tensorflow::Device* input_device;
TF_RETURN_IF_ERROR(op->Inputs()[i]->Device(&input_device));
- if (op->Device() != input_device) {
+ if (op->Device() != input_device &&
+ // If the expected and actual devices are on the same task, don't
+ // explicitly copy, and instead depend on the copy to happen locally
+ // when the op is executed on the device.
+ !OnSameTask(ctx, op->Device(), input_device)) {
// TODO(b/110044833): It's possible the same tensor gets copied to the
// remote device repeatedly.
TF_RETURN_IF_ERROR(MaybeCopyInputToExpectedDevice(
diff --git a/tensorflow/core/common_runtime/function_test.cc b/tensorflow/core/common_runtime/function_test.cc
index 1e837e9a7e..120f480198 100644
--- a/tensorflow/core/common_runtime/function_test.cc
+++ b/tensorflow/core/common_runtime/function_test.cc
@@ -1019,8 +1019,9 @@ TEST_F(FunctionLibraryRuntimeTest, Error_BadControlFlow) {
DCHECK_EQ(x.dtype(), DT_INT32);
Tensor y;
HasError(InstantiateAndRun(flr0_, "InvalidControlFlow", {}, {x}, {&y}),
- "The node 'add' has inputs from different frames. The input 'enter' "
- "is in frame 'while'. The input 'i' is in frame ''.");
+ "{{node add}} has inputs from different frames. The input"
+ " {{node enter}} is in frame 'while'. The input {{node i}} is in"
+ " frame ''.");
}
TEST_F(FunctionLibraryRuntimeTest, Gradient_XTimesTwo) {
diff --git a/tensorflow/core/common_runtime/mkl_cpu_allocator.h b/tensorflow/core/common_runtime/mkl_cpu_allocator.h
index 29f702699f..94e10dbfa2 100644
--- a/tensorflow/core/common_runtime/mkl_cpu_allocator.h
+++ b/tensorflow/core/common_runtime/mkl_cpu_allocator.h
@@ -22,7 +22,6 @@ limitations under the License.
#ifdef INTEL_MKL
#include <cstdlib>
-#include <string>
#include "tensorflow/core/common_runtime/bfc_allocator.h"
#include "tensorflow/core/common_runtime/visitable_allocator.h"
#include "tensorflow/core/lib/strings/numbers.h"
diff --git a/tensorflow/core/common_runtime/placer.cc b/tensorflow/core/common_runtime/placer.cc
index 6781c87f6c..613470365d 100644
--- a/tensorflow/core/common_runtime/placer.cc
+++ b/tensorflow/core/common_runtime/placer.cc
@@ -41,10 +41,8 @@ namespace {
const StringPiece kColocationAttrNameStringPiece(kColocationAttrName);
const StringPiece kColocationGroupPrefixStringPiece(kColocationGroupPrefix);
-// Returns a list of devices sorted by preferred type and then name
-// from 'devices' whose type is in 'supported_device_types'. This
-// function searches the device types in 'supported_device_types' and
-// returns the subset of devices that match.
+// Returns a list of devices having type in supported_device_types. The
+// returned list is sorted by preferred type (higher numeric type is preferred).
std::vector<Device*> FilterSupportedDevices(
const std::vector<Device*>& devices,
const DeviceTypeVector& supported_device_types) {
@@ -81,12 +79,12 @@ std::vector<Device*> FilterSupportedDevices(
// DeviceSet device_set = ...;
// ColocationGraph colocation_graph(graph, device_set);
//
-// // Add all the nodes of graph to colocation_graph.
+// // Add all the nodes of the `graph` to the `colocation_graph`.
// for (Node* node : graph.nodes()) {
// TF_RETURN_IF_ERROR(colocation_graph.AddNode(*node));
// }
//
-// // Add one or more colocation constraint.
+// // Add one or more colocation constraints.
// Node node_1 = *graph.FindNodeId(...);
// Node node_2 = *graph.FindNodeId(...);
// TF_RETURN_IF_ERROR(colocation_graph.ColocateNodes(node_1, node_2));
@@ -96,9 +94,9 @@ std::vector<Device*> FilterSupportedDevices(
// TF_RETURN_IF_ERROR(colocation_graph.AssignDevice(node));
// }
//
-// The implementation uses the union-find algorithm to maintain the
-// connected components efficiently and incrementally as edges
-// (implied by ColocationGraph::ColocateNodes() invocations) are added.
+// This implementation uses the Union-Find algorithm to efficiently maintain the
+// connected components and incrementally adds edges via
+// ColocationGraph::ColocateNodes() invocations.
class ColocationGraph {
public:
ColocationGraph(Graph* graph, const DeviceSet* device_set,
@@ -134,13 +132,9 @@ class ColocationGraph {
std::unordered_map<StringPiece, const Node*, StringPieceHasher>
colocation_group_root;
- for (Node* node : graph_->nodes()) {
- if (!node->IsOp()) {
- continue;
- }
-
- // When adding the node, identify whether it is part of a
- // colocation group.
+ for (Node* node : graph_->op_nodes()) {
+ // When adding the node, identify whether it is part of a colocation
+ // group.
// This code is effectively the equivalent of GetNodeAttr() for a string
// array, but it avoids all internal allocations (the allocation of the
@@ -219,11 +213,10 @@ class ColocationGraph {
Member& x_root_member = members_[x_root];
Member& y_root_member = members_[y_root];
- // Merge the sets by swinging the parent pointer of the smaller
- // tree to point to the root of the larger tree. Together with
- // path compression in ColocationGraph::FindRoot, this ensures
- // that we do not experience pathological performance on graphs
- // such as chains.
+ // Merge the sets by setting the parent pointer of the smaller tree's root
+ // node to point to the root of the larger tree. Together with path
+ // compression in ColocationGraph::FindRoot, this ensures that we do not
+ // experience pathological performance on graphs such as chains.
int new_root, old_root;
if (x_root_member.rank < y_root_member.rank) {
// The tree rooted at x_root is shallower, so connect it to
@@ -611,22 +604,16 @@ class ColocationGraph {
// given id is connected.
int FindRoot(int node_id) {
Member& member = members_[node_id];
-
- int parent = member.parent;
- DCHECK_GE(parent, 0);
-
- if (parent != node_id) {
- // NOTE: Compress paths from node_id to its root, so that future
- // calls to FindRoot and ColocateNodes are more efficient.
- int root = FindRoot(parent);
- if (parent != root) {
- parent = root;
- member.parent = root;
- }
+ DCHECK_GE(member.parent, 0);
+ if (member.parent == node_id) {
+ // member.parent is the root of this disjoint tree. Do nothing.
+ } else {
+ member.parent = FindRoot(member.parent);
}
-
- DCHECK_GE(parent, 0);
- return parent;
+ // Now it is guaranteed that member.parent is the root of this disjoint
+ // tree.
+ DCHECK_GE(member.parent, 0);
+ return member.parent;
}
// Ensures that the devices of 'dst's resource and reference match the device
diff --git a/tensorflow/core/common_runtime/session_ref.cc b/tensorflow/core/common_runtime/session_ref.cc
new file mode 100644
index 0000000000..b931ef4229
--- /dev/null
+++ b/tensorflow/core/common_runtime/session_ref.cc
@@ -0,0 +1,170 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#include "tensorflow/core/common_runtime/session_ref.h"
+
+#include <utility>
+
+namespace tensorflow {
+
+namespace {
+
+// Scope helper to track active calls and manage session lifetime.
+struct RunCounter {
+ std::shared_ptr<Session> session;
+ uint64* value;
+ mutex* m;
+ condition_variable* cv;
+
+ explicit RunCounter(std::shared_ptr<Session> s, uint64* v, mutex* m,
+ condition_variable* cv)
+ : session(std::move(s)), value(v), m(m), cv(cv) {
+ mutex_lock l(*m);
+ ++*value;
+ }
+
+ ~RunCounter() {
+ mutex_lock l(*m);
+ if (--*value == 0) {
+ cv->notify_all();
+ }
+ }
+};
+
+} // namespace
+
+Status SessionRef::CheckNotClosed() {
+ mutex_lock l(run_lock_);
+ if (session_ == nullptr) return errors::Cancelled("Session has been closed.");
+ return ::tensorflow::Status::OK();
+}
+
+Status SessionRef::Run(const RunOptions& run_options,
+ const std::vector<std::pair<string, Tensor> >& inputs,
+ const std::vector<string>& output_tensor_names,
+ const std::vector<string>& target_node_names,
+ std::vector<Tensor>* outputs,
+ RunMetadata* run_metadata) {
+ TF_RETURN_IF_ERROR(CheckNotClosed());
+ RunCounter rc(session_, &run_count_, &run_lock_, &run_finished_);
+ return rc.session->Run(run_options, inputs, output_tensor_names,
+ target_node_names, outputs, run_metadata);
+}
+
+Status SessionRef::Create(const GraphDef& graph) {
+ TF_RETURN_IF_ERROR(CheckNotClosed());
+ RunCounter rc(session_, &run_count_, &run_lock_, &run_finished_);
+ return rc.session->Create(graph);
+}
+
+Status SessionRef::Create(const RunOptions& run_options,
+ const GraphDef& graph) {
+ TF_RETURN_IF_ERROR(CheckNotClosed());
+ RunCounter rc(session_, &run_count_, &run_lock_, &run_finished_);
+ return rc.session->Create(run_options, graph);
+}
+
+Status SessionRef::Extend(const RunOptions& run_options,
+ const GraphDef& graph) {
+ TF_RETURN_IF_ERROR(CheckNotClosed());
+ RunCounter rc(session_, &run_count_, &run_lock_, &run_finished_);
+ return rc.session->Extend(run_options, graph);
+}
+
+Status SessionRef::Extend(const GraphDef& graph) {
+ TF_RETURN_IF_ERROR(CheckNotClosed());
+ RunCounter rc(session_, &run_count_, &run_lock_, &run_finished_);
+ return rc.session->Extend(graph);
+}
+
+Status SessionRef::Close(const RunOptions& run_options) {
+ TF_RETURN_IF_ERROR(CheckNotClosed());
+ mutex_lock l(run_lock_);
+ Status status = session_->Close(run_options);
+ session_.reset();
+ while (run_count_ > 0) {
+ run_finished_.wait(l);
+ }
+ return status;
+}
+
+Status SessionRef::Close() {
+ TF_RETURN_IF_ERROR(CheckNotClosed());
+ mutex_lock l(run_lock_);
+ Status status = session_->Close();
+ session_.reset();
+ while (run_count_ > 0) {
+ run_finished_.wait(l);
+ }
+ return status;
+}
+
+Status SessionRef::Run(const std::vector<std::pair<string, Tensor> >& inputs,
+ const std::vector<string>& output_tensor_names,
+ const std::vector<string>& target_node_names,
+ std::vector<Tensor>* outputs) {
+ TF_RETURN_IF_ERROR(CheckNotClosed());
+ RunCounter rc(session_, &run_count_, &run_lock_, &run_finished_);
+ return rc.session->Run(inputs, output_tensor_names, target_node_names,
+ outputs);
+}
+
+Status SessionRef::ListDevices(std::vector<DeviceAttributes>* response) {
+ TF_RETURN_IF_ERROR(CheckNotClosed());
+ RunCounter rc(session_, &run_count_, &run_lock_, &run_finished_);
+ return rc.session->ListDevices(response);
+}
+
+Status SessionRef::PRunSetup(const std::vector<string>& input_names,
+ const std::vector<string>& output_names,
+ const std::vector<string>& target_nodes,
+ string* handle) {
+ TF_RETURN_IF_ERROR(CheckNotClosed());
+ RunCounter rc(session_, &run_count_, &run_lock_, &run_finished_);
+ return rc.session->PRunSetup(input_names, output_names, target_nodes, handle);
+}
+
+Status SessionRef::PRun(const string& handle,
+ const std::vector<std::pair<string, Tensor> >& inputs,
+ const std::vector<string>& output_names,
+ std::vector<Tensor>* outputs) {
+ TF_RETURN_IF_ERROR(CheckNotClosed());
+ RunCounter rc(session_, &run_count_, &run_lock_, &run_finished_);
+ return rc.session->PRun(handle, inputs, output_names, outputs);
+}
+
+Status SessionRef::MakeCallable(const CallableOptions& callable_options,
+ CallableHandle* out_handle) {
+ TF_RETURN_IF_ERROR(CheckNotClosed());
+ RunCounter rc(session_, &run_count_, &run_lock_, &run_finished_);
+ return rc.session->MakeCallable(callable_options, out_handle);
+}
+
+Status SessionRef::RunCallable(CallableHandle handle,
+ const std::vector<Tensor>& feed_tensors,
+ std::vector<Tensor>* fetch_tensors,
+ RunMetadata* run_metadata) {
+ TF_RETURN_IF_ERROR(CheckNotClosed());
+ RunCounter rc(session_, &run_count_, &run_lock_, &run_finished_);
+ return rc.session->RunCallable(handle, feed_tensors, fetch_tensors,
+ run_metadata);
+}
+
+Status SessionRef::ReleaseCallable(CallableHandle handle) {
+ TF_RETURN_IF_ERROR(CheckNotClosed());
+ RunCounter rc(session_, &run_count_, &run_lock_, &run_finished_);
+ return rc.session->ReleaseCallable(handle);
+}
+
+} // namespace tensorflow
diff --git a/tensorflow/core/common_runtime/session_ref.h b/tensorflow/core/common_runtime/session_ref.h
new file mode 100644
index 0000000000..9459e7edbe
--- /dev/null
+++ b/tensorflow/core/common_runtime/session_ref.h
@@ -0,0 +1,86 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#ifndef TENSORFLOW_CORE_COMMON_RUNTIME_SESSION_REF_H_
+#define TENSORFLOW_CORE_COMMON_RUNTIME_SESSION_REF_H_
+
+#include <memory>
+
+#include "tensorflow/core/platform/mutex.h"
+#include "tensorflow/core/public/session.h"
+
+namespace tensorflow {
+
+// A `SessionRef` manages the lifetime of a wrapped `Session` pointer.
+//
+// SessionRef blocks the return of Close() until all pending operations have
+// been completed or cancelled and underlying session has been freed. Any
+// subsequent operations on the SessionRef object will return errors::Cancelled.
+class SessionRef : public Session {
+ public:
+ SessionRef(Session* session) : session_(session) {}
+ virtual ~SessionRef() {}
+
+ Status Create(const GraphDef& graph) override;
+ Status Extend(const GraphDef& graph) override;
+ Status Create(const RunOptions& run_options, const GraphDef& graph) override;
+ Status Extend(const RunOptions& run_options, const GraphDef& graph) override;
+ Status Run(const std::vector<std::pair<string, Tensor> >& inputs,
+ const std::vector<string>& output_tensor_names,
+ const std::vector<string>& target_node_names,
+ std::vector<Tensor>* outputs) override;
+
+ Status ListDevices(std::vector<DeviceAttributes>* response) override;
+
+ Status Close() override;
+ Status Close(const RunOptions& run_options) override;
+
+ Status Run(const RunOptions& run_options,
+ const std::vector<std::pair<string, Tensor> >& inputs,
+ const std::vector<string>& output_tensor_names,
+ const std::vector<string>& target_node_names,
+ std::vector<Tensor>* outputs, RunMetadata* run_metadata) override;
+
+ Status PRunSetup(const std::vector<string>& input_names,
+ const std::vector<string>& output_names,
+ const std::vector<string>& target_nodes,
+ string* handle) override;
+
+ Status PRun(const string& handle,
+ const std::vector<std::pair<string, Tensor> >& inputs,
+ const std::vector<string>& output_names,
+ std::vector<Tensor>* outputs) override;
+
+ Status MakeCallable(const CallableOptions& callable_options,
+ CallableHandle* out_handle) override;
+
+ Status RunCallable(CallableHandle handle,
+ const std::vector<Tensor>& feed_tensors,
+ std::vector<Tensor>* fetch_tensors,
+ RunMetadata* run_metadata) override;
+
+ Status ReleaseCallable(CallableHandle handle) override;
+
+ private:
+ mutex run_lock_;
+ condition_variable run_finished_;
+ uint64 run_count_ GUARDED_BY(run_lock_) = {0};
+ std::shared_ptr<Session> session_;
+
+ Status CheckNotClosed();
+};
+
+} // namespace tensorflow
+
+#endif // TENSORFLOW_CORE_COMMON_RUNTIME_SESSION_REF_H_
diff --git a/tensorflow/core/common_runtime/threadpool_device_factory.cc b/tensorflow/core/common_runtime/threadpool_device_factory.cc
index 61a62621ba..6a900c02c0 100644
--- a/tensorflow/core/common_runtime/threadpool_device_factory.cc
+++ b/tensorflow/core/common_runtime/threadpool_device_factory.cc
@@ -19,7 +19,6 @@ limitations under the License.
#include <vector>
#include "tensorflow/core/common_runtime/device_factory.h"
#include "tensorflow/core/framework/allocator.h"
-#include "tensorflow/core/lib/io/path.h"
#include "tensorflow/core/public/session_options.h"
namespace tensorflow {
diff --git a/tensorflow/core/distributed_runtime/rpc/grpc_server_lib.cc b/tensorflow/core/distributed_runtime/rpc/grpc_server_lib.cc
index db14f6473e..8a6903be9e 100644
--- a/tensorflow/core/distributed_runtime/rpc/grpc_server_lib.cc
+++ b/tensorflow/core/distributed_runtime/rpc/grpc_server_lib.cc
@@ -244,6 +244,7 @@ Status GrpcServer::Init(
// Finish setting up master environment.
master_env_.ops = OpRegistry::Global();
master_env_.worker_cache = worker_cache;
+ master_env_.collective_executor_mgr = worker_env_.collective_executor_mgr;
master_env_.master_session_factory =
[config, stats_factory](
SessionOptions options, const MasterEnv* env,
diff --git a/tensorflow/core/framework/bfloat16_test.cc b/tensorflow/core/framework/bfloat16_test.cc
index 206396a25a..0a1b5e1975 100644
--- a/tensorflow/core/framework/bfloat16_test.cc
+++ b/tensorflow/core/framework/bfloat16_test.cc
@@ -45,7 +45,8 @@ class Bfloat16Test : public ::testing::Test,
public ::testing::WithParamInterface<Bfloat16TestParam> {};
TEST_P(Bfloat16Test, TruncateTest) {
- bfloat16 truncated(GetParam().input);
+ bfloat16 truncated = bfloat16::truncate_to_bfloat16((GetParam().input));
+
if (std::isnan(GetParam().input)) {
EXPECT_TRUE(std::isnan(float(truncated)) || std::isinf(float(truncated)));
return;
diff --git a/tensorflow/core/framework/dataset.h b/tensorflow/core/framework/dataset.h
index d8618f391e..8cf84afedb 100644
--- a/tensorflow/core/framework/dataset.h
+++ b/tensorflow/core/framework/dataset.h
@@ -498,28 +498,24 @@ class GraphDatasetBase : public DatasetBase {
};
// Represents an iterator that is associated with a particular parent dataset.
-template <class DatasetType>
-class DatasetIterator : public IteratorBase {
+class DatasetBaseIterator : public IteratorBase {
public:
- struct Params {
- // Owns one reference on the shared dataset resource.
- const DatasetType* dataset;
+ struct BaseParams {
+ // Owns one reference on the shared dataset object.
+ const DatasetBase* dataset;
// Identifies the sequence of iterators leading up to this iterator.
const string prefix;
};
- explicit DatasetIterator(const Params& params) : params_(params) {
+ explicit DatasetBaseIterator(const BaseParams& params) : params_(params) {
params_.dataset->Ref();
}
- ~DatasetIterator() override { params_.dataset->Unref(); }
-
- // The dataset from which this iterator was created.
- const DatasetType* dataset() const { return params_.dataset; }
+ ~DatasetBaseIterator() override { params_.dataset->Unref(); }
// The sequence of iterators leading up to this iterator.
- const string prefix() const { return params_.prefix; }
+ const string& prefix() const { return params_.prefix; }
const DataTypeVector& output_dtypes() const override {
return params_.dataset->output_dtypes();
@@ -545,7 +541,7 @@ class DatasetIterator : public IteratorBase {
}
Status Save(OpKernelContext* ctx, IteratorStateWriter* writer) final {
- TF_RETURN_IF_ERROR(dataset()->Save(ctx, writer));
+ TF_RETURN_IF_ERROR(params_.dataset->Save(ctx, writer));
return IteratorBase::Save(ctx, writer);
}
@@ -556,11 +552,40 @@ class DatasetIterator : public IteratorBase {
bool* end_of_sequence) = 0;
string full_name(const string& name) const {
- return strings::StrCat(prefix(), ":", name);
+ return strings::StrCat(params_.prefix, ":", name);
}
private:
- Params params_;
+ BaseParams params_;
+};
+
+// Represents an iterator that is associated with a particular parent dataset
+// with a particular type.
+template <class DatasetType>
+class DatasetIterator : public DatasetBaseIterator {
+ public:
+ struct Params {
+ // Borrowed pointer to the parent dataset.
+ const DatasetType* dataset;
+
+ // Identifies the sequence of iterators leading up to this iterator.
+ const string prefix;
+ };
+
+ explicit DatasetIterator(const Params& params)
+ : DatasetBaseIterator({params.dataset, params.prefix}),
+ typed_dataset_(params.dataset) {}
+
+ // The dataset from which this iterator was created.
+ const DatasetType* dataset() const { return typed_dataset_; }
+
+ protected:
+ virtual Status GetNextInternal(IteratorContext* ctx,
+ std::vector<Tensor>* out_tensors,
+ bool* end_of_sequence) = 0;
+
+ private:
+ const DatasetType* const typed_dataset_; // Not owned.
};
// Encapsulates the work required to plug a DatasetBase into the core TensorFlow
diff --git a/tensorflow/core/framework/function_testlib.cc b/tensorflow/core/framework/function_testlib.cc
index 2b5a0fe1bb..a8eecc1a63 100644
--- a/tensorflow/core/framework/function_testlib.cc
+++ b/tensorflow/core/framework/function_testlib.cc
@@ -45,13 +45,12 @@ GraphDef GDef(gtl::ArraySlice<NodeDef> nodes,
}
// Helper to construct a NodeDef.
-NodeDef NDef(const string& name, const string& op,
- gtl::ArraySlice<string> inputs,
+NodeDef NDef(StringPiece name, StringPiece op, gtl::ArraySlice<string> inputs,
gtl::ArraySlice<std::pair<string, FDH::AttrValueWrapper>> attrs,
const string& device) {
NodeDef n;
- n.set_name(name);
- n.set_op(op);
+ n.set_name(name.ToString());
+ n.set_op(op.ToString());
for (const auto& in : inputs) n.add_input(in);
n.set_device(device);
for (auto na : attrs) n.mutable_attr()->insert({na.first, na.second.proto});
diff --git a/tensorflow/core/framework/function_testlib.h b/tensorflow/core/framework/function_testlib.h
index b67c5cb1ab..8cf3c6a680 100644
--- a/tensorflow/core/framework/function_testlib.h
+++ b/tensorflow/core/framework/function_testlib.h
@@ -48,7 +48,7 @@ class Attrs {
// Helper to construct a NodeDef.
NodeDef NDef(
- const string& name, const string& op, gtl::ArraySlice<string> inputs,
+ StringPiece name, StringPiece op, gtl::ArraySlice<string> inputs,
gtl::ArraySlice<std::pair<string, FunctionDefHelper::AttrValueWrapper>>
attrs = {},
const string& device = "");
diff --git a/tensorflow/core/framework/node_def_util.cc b/tensorflow/core/framework/node_def_util.cc
index e8ea904ebd..0bd79366eb 100644
--- a/tensorflow/core/framework/node_def_util.cc
+++ b/tensorflow/core/framework/node_def_util.cc
@@ -86,7 +86,8 @@ string AttrSlice::SummarizeNode() const {
string SummarizeNode(const Node& node) { return SummarizeNodeDef(node.def()); }
string SummarizeNodeDef(const NodeDef& node_def) {
- string ret = strings::StrCat(node_def.name(), " = ", node_def.op(), "[");
+ string ret = strings::StrCat(FormatNodeDefForError(node_def), " = ",
+ node_def.op(), "[");
strings::StrAppend(&ret, SummarizeAttrsHelper(node_def, node_def.device()));
strings::StrAppend(&ret, "](");
@@ -101,6 +102,14 @@ string SummarizeNodeDef(const NodeDef& node_def) {
return ret;
}
+string FormatNodeForError(const Node& node) {
+ return FormatNodeDefForError(node.def());
+}
+
+string FormatNodeDefForError(const NodeDef& node_def) {
+ return errors::FormatNodeNameForError(node_def.name());
+}
+
const AttrValue* AttrSlice::Find(StringPiece attr_name) const {
// Currently, the collection used for NodeDef::attr() (google::protobuf::Map)
// requires that the keys used for lookups have type 'const string&'. Because
@@ -634,7 +643,7 @@ Status ValidateExternalNodeDefSyntax(const NodeDef& node_def) {
Status AttachDef(const Status& status, const NodeDef& node_def) {
Status ret = status;
errors::AppendToMessage(
- &ret, strings::StrCat(" [[Node: ", SummarizeNodeDef(node_def), "]]"));
+ &ret, strings::StrCat(" [[", SummarizeNodeDef(node_def), "]]"));
return ret;
}
diff --git a/tensorflow/core/framework/node_def_util.h b/tensorflow/core/framework/node_def_util.h
index 64c8b386e8..c012b7c3d3 100644
--- a/tensorflow/core/framework/node_def_util.h
+++ b/tensorflow/core/framework/node_def_util.h
@@ -50,6 +50,12 @@ extern const char* const kColocationGroupPrefix;
string SummarizeNode(const Node& node);
string SummarizeNodeDef(const NodeDef& node_def);
+// Produces a formatted string pattern from the node which can uniquely identify
+// this node upstream to produce an informative error message. The pattern
+// followed is: {{node <node_name>}}
+string FormatNodeForError(const Node& node);
+string FormatNodeDefForError(const NodeDef& node_def);
+
typedef protobuf::Map<string, AttrValue> AttrValueMap;
// Adds an attr with name <name> and value <value> to *node_def.
diff --git a/tensorflow/core/framework/node_def_util_test.cc b/tensorflow/core/framework/node_def_util_test.cc
index 35b7b2272b..74cc594863 100644
--- a/tensorflow/core/framework/node_def_util_test.cc
+++ b/tensorflow/core/framework/node_def_util_test.cc
@@ -20,6 +20,8 @@ limitations under the License.
#include "tensorflow/core/framework/node_def_builder.h"
#include "tensorflow/core/framework/op_def_builder.h"
#include "tensorflow/core/framework/op_def_util.h"
+#include "tensorflow/core/graph/graph.h"
+#include "tensorflow/core/graph/node_builder.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/core/status_test_util.h"
#include "tensorflow/core/lib/strings/str_util.h"
@@ -79,7 +81,7 @@ TEST(NodeDefUtilTest, In) {
)proto");
ExpectSuccess(node_def, op);
- EXPECT_EQ("n = In[T=DT_FLOAT](a)", SummarizeNodeDef(node_def));
+ EXPECT_EQ("{{node n}} = In[T=DT_FLOAT](a)", SummarizeNodeDef(node_def));
// Mismatching Op names.
NodeDef bad = node_def;
@@ -144,7 +146,7 @@ TEST(NodeDefUtilTest, Out) {
)proto");
ExpectSuccess(node_def, op);
- EXPECT_EQ("n = Out[T=DT_INT32]()", SummarizeNodeDef(node_def));
+ EXPECT_EQ("{{node n}} = Out[T=DT_INT32]()", SummarizeNodeDef(node_def));
// Non-number type.
NodeDef bad = node_def;
@@ -164,7 +166,7 @@ TEST(NodeDefUtilTest, Enum) {
)proto");
ExpectSuccess(node_def, op);
- EXPECT_EQ("n = Enum[e=\"apple\"]()", SummarizeNodeDef(node_def));
+ EXPECT_EQ("{{node n}} = Enum[e=\"apple\"]()", SummarizeNodeDef(node_def));
NodeDef good = node_def;
good.clear_attr();
@@ -191,7 +193,8 @@ TEST(NodeDefUtilTest, SameIn) {
)proto");
ExpectSuccess(node_def, op);
- EXPECT_EQ("n = SameIn[N=2, T=DT_DOUBLE](a, b)", SummarizeNodeDef(node_def));
+ EXPECT_EQ("{{node n}} = SameIn[N=2, T=DT_DOUBLE](a, b)",
+ SummarizeNodeDef(node_def));
// Illegal type
NodeDef bad = ToNodeDef(R"proto(
@@ -220,7 +223,7 @@ TEST(NodeDefUtilTest, AnyIn) {
)proto");
ExpectSuccess(node_def, op);
- EXPECT_EQ("n = AnyIn[T=[DT_INT32, DT_STRING]](a, b)",
+ EXPECT_EQ("{{node n}} = AnyIn[T=[DT_INT32, DT_STRING]](a, b)",
SummarizeNodeDef(node_def));
const NodeDef bad = ToNodeDef(R"proto(
@@ -243,13 +246,14 @@ TEST(NodeDefUtilTest, Device) {
const NodeDef node_def1 =
ToNodeDef(NodeDefBuilder("d", &op_def1).Device("/cpu:17"));
ExpectSuccess(node_def1, op_def1);
- EXPECT_EQ("d = None[_device=\"/cpu:17\"]()", SummarizeNodeDef(node_def1));
+ EXPECT_EQ("{{node d}} = None[_device=\"/cpu:17\"]()",
+ SummarizeNodeDef(node_def1));
const OpDef op_def2 = ToOpDef(OpDefBuilder("WithAttr").Attr("v: int"));
const NodeDef node_def2 =
ToNodeDef(NodeDefBuilder("d", &op_def2).Attr("v", 7).Device("/cpu:5"));
ExpectSuccess(node_def2, op_def2);
- EXPECT_EQ("d = WithAttr[v=7, _device=\"/cpu:5\"]()",
+ EXPECT_EQ("{{node d}} = WithAttr[v=7, _device=\"/cpu:5\"]()",
SummarizeNodeDef(node_def2));
}
@@ -284,7 +288,7 @@ TEST(NodeDefUtilTest, ValidSyntax) {
)proto");
ExpectValidSyntax(node_def_explicit_inputs);
- EXPECT_EQ("n = AnyIn[T=[DT_INT32, DT_STRING]](a:0, b:123)",
+ EXPECT_EQ("{{node n}} = AnyIn[T=[DT_INT32, DT_STRING]](a:0, b:123)",
SummarizeNodeDef(node_def_explicit_inputs));
const NodeDef node_def_partial_shape = ToNodeDef(R"proto(
@@ -379,7 +383,7 @@ TEST(NameRangesForNodeTest, Simple) {
EXPECT_EQ(NameRangeMap({{"a", {0, 1}}, {"b", {1, 2}}}), inputs);
EXPECT_EQ(NameRangeMap({{"c", {0, 1}}, {"d", {1, 2}}}), outputs);
- EXPECT_EQ("simple = Simple[](a, b)", SummarizeNodeDef(node_def));
+ EXPECT_EQ("{{node simple}} = Simple[](a, b)", SummarizeNodeDef(node_def));
OpDef bad_op_def = op_def;
bad_op_def.mutable_input_arg(0)->clear_type();
@@ -399,7 +403,7 @@ TEST(NameRangesForNodeTest, Polymorphic) {
TF_EXPECT_OK(NameRangesForNode(node_def1, op_def, &inputs, &outputs));
EXPECT_EQ(NameRangeMap({{"a", {0, 1}}, {"b", {1, 2}}}), inputs);
EXPECT_EQ(NameRangeMap({{"c", {0, 1}}}), outputs);
- EXPECT_EQ("poly = Polymorphic[T=DT_INT32](a, b)",
+ EXPECT_EQ("{{node poly}} = Polymorphic[T=DT_INT32](a, b)",
SummarizeNodeDef(node_def1));
const NodeDef node_def2 = ToNodeDef(NodeDefBuilder("poly", &op_def)
@@ -408,7 +412,8 @@ TEST(NameRangesForNodeTest, Polymorphic) {
TF_EXPECT_OK(NameRangesForNode(node_def2, op_def, &inputs, &outputs));
EXPECT_EQ(NameRangeMap({{"a", {0, 1}}, {"b", {1, 2}}}), inputs);
EXPECT_EQ(NameRangeMap({{"c", {0, 1}}}), outputs);
- EXPECT_EQ("poly = Polymorphic[T=DT_BOOL](a, b)", SummarizeNodeDef(node_def2));
+ EXPECT_EQ("{{node poly}} = Polymorphic[T=DT_BOOL](a, b)",
+ SummarizeNodeDef(node_def2));
}
TEST(NameRangesForNodeTest, NRepeats) {
@@ -431,7 +436,8 @@ TEST(NameRangesForNodeTest, NRepeats) {
EXPECT_EQ(NameRangeMap({{"c", {0, 1}}, {"d", {1, 5}}, {"e", {5, 8}}}),
outputs);
EXPECT_EQ(
- "nr = NRepeats[M=3, N=4, T=DT_FLOAT](a, a:1, a:2, a:3, b, b:1, b:2, b:3)",
+ "{{node nr}} = NRepeats[M=3, N=4, T=DT_FLOAT](a, a:1, a:2, a:3, b, b:1, "
+ "b:2, b:3)",
SummarizeNodeDef(node_def1));
const NodeDef node_def2 = ToNodeDef(NodeDefBuilder("nr", &op_def)
@@ -442,7 +448,7 @@ TEST(NameRangesForNodeTest, NRepeats) {
EXPECT_EQ(NameRangeMap({{"a", {0, 2}}, {"b", {2, 4}}}), inputs);
EXPECT_EQ(NameRangeMap({{"c", {0, 1}}, {"d", {1, 3}}, {"e", {3, 10}}}),
outputs);
- EXPECT_EQ("nr = NRepeats[M=7, N=2, T=DT_DOUBLE](a, a:1, b, b:1)",
+ EXPECT_EQ("{{node nr}} = NRepeats[M=7, N=2, T=DT_DOUBLE](a, a:1, b, b:1)",
SummarizeNodeDef(node_def2));
NodeDef bad_node_def = node_def2;
@@ -471,7 +477,7 @@ TEST(NameRangesForNodeTest, TypeList) {
EXPECT_EQ(NameRangeMap({{"c", {0, 4}}, {"d", {4, 7}}, {"e", {7, 9}}}),
outputs);
EXPECT_EQ(
- "tl = TypeList[T1=[DT_BOOL, DT_FLOAT],"
+ "{{node tl}} = TypeList[T1=[DT_BOOL, DT_FLOAT],"
" T2=[DT_FLOAT, DT_FLOAT, DT_FLOAT, DT_FLOAT],"
" T3=[DT_INT32, DT_DOUBLE, DT_STRING]](a, a:1, b, b:1, b:2, b:3)",
SummarizeNodeDef(node_def1));
@@ -485,7 +491,8 @@ TEST(NameRangesForNodeTest, TypeList) {
EXPECT_EQ(NameRangeMap({{"c", {0, 1}}, {"d", {1, 3}}, {"e", {3, 10}}}),
outputs);
EXPECT_EQ(
- "tl = TypeList[T1=[DT_INT32, DT_INT32, DT_INT32, DT_INT32, DT_INT32,"
+ "{{node tl}} = TypeList[T1=[DT_INT32, DT_INT32, DT_INT32, DT_INT32, "
+ "DT_INT32,"
" DT_INT32, DT_INT32], T2=[DT_DOUBLE], T3=[DT_DOUBLE, DT_STRING]]"
"(a, a:1, a:2, a:3, a:4, a:5, a:6, b)",
SummarizeNodeDef(node_def2));
@@ -509,5 +516,20 @@ TEST(AddPrefixAndSuffixToNode, Enter) {
EXPECT_EQ("prefix/test_frame/suffix", frame_name);
}
+TEST(FormatNodeForErrorTest, Node) {
+ Graph g(OpRegistry::Global());
+ Node* node;
+ TF_CHECK_OK(NodeBuilder("enter", "NoOp").Finalize(&g, &node));
+ EXPECT_EQ("{{node enter}}", FormatNodeForError(*node));
+}
+
+TEST(FormatNodeForErrorTest, NodeDef) {
+ NodeDef node_def;
+ node_def.set_name("enter");
+ node_def.set_op("Enter");
+ AddNodeAttr("frame_name", "test_frame", &node_def);
+ EXPECT_EQ("{{node enter}}", FormatNodeDefForError(node_def));
+}
+
} // namespace
} // namespace tensorflow
diff --git a/tensorflow/core/framework/op_compatibility_test.cc b/tensorflow/core/framework/op_compatibility_test.cc
index c782480f1f..140f201085 100644
--- a/tensorflow/core/framework/op_compatibility_test.cc
+++ b/tensorflow/core/framework/op_compatibility_test.cc
@@ -209,8 +209,8 @@ TEST_F(OpCompatibilityTest, Same) {
.Finalize(node_def()));
ExpectSuccess(*RegisteredOpDef());
EXPECT_EQ(
- "same = Same[N=3, T=DT_FLOAT, TList=[DT_BOOL, DT_BOOL]](a, b, c, c:1, "
- "c:2, d, d:1, d:2, e, e:1)",
+ "{{node same}} = Same[N=3, T=DT_FLOAT, TList=[DT_BOOL, DT_BOOL]](a, b, "
+ "c, c:1, c:2, d, d:1, d:2, e, e:1)",
Result());
}
@@ -224,7 +224,7 @@ TEST_F(OpCompatibilityTest, AddAttr) {
OpDefBuilder("AddAttr").Output("ndef: string").Finalize(&old_op));
TF_ASSERT_OK(NodeDefBuilder("add_attr", &old_op.op_def).Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("add_attr = AddAttr[a=42]()", Result());
+ EXPECT_EQ("{{node add_attr}} = AddAttr[a=42]()", Result());
}
// Should be able to make an attr restriction less strict.
@@ -241,7 +241,7 @@ TEST_F(OpCompatibilityTest, LessStrict) {
.Attr("a", "B")
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("less_strict = LessStrict[a=\"B\"]()", Result());
+ EXPECT_EQ("{{node less_strict}} = LessStrict[a=\"B\"]()", Result());
}
// Should be able to remove an attr restriction.
@@ -259,7 +259,8 @@ TEST_F(OpCompatibilityTest, RemoveRestriction) {
.Attr("a", DT_INT32)
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("remove_restriction = RemoveRestriction[a=DT_INT32]()", Result());
+ EXPECT_EQ("{{node remove_restriction}} = RemoveRestriction[a=DT_INT32]()",
+ Result());
}
// Should be able to change the order of attrs.
@@ -278,7 +279,7 @@ TEST_F(OpCompatibilityTest, AttrOrder) {
.Attr("a", 7)
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("attr_order = AttrOrder[a=7, b=true]()", Result());
+ EXPECT_EQ("{{node attr_order}} = AttrOrder[a=7, b=true]()", Result());
}
// Should be able to make an input/output polymorphic.
@@ -299,7 +300,8 @@ TEST_F(OpCompatibilityTest, TypePolymorphic) {
.Input(FakeInput())
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("type_polymorphic = TypePolymorphic[T=DT_INT32](a)", Result());
+ EXPECT_EQ("{{node type_polymorphic}} = TypePolymorphic[T=DT_INT32](a)",
+ Result());
}
// Should be able to make a single input/output into a list.
@@ -320,7 +322,7 @@ TEST_F(OpCompatibilityTest, MakeList) {
.Input(FakeInput())
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("make_list = MakeList[N=1](a)", Result());
+ EXPECT_EQ("{{node make_list}} = MakeList[N=1](a)", Result());
}
// Should be able to make a single input/output into a polymorphic list.
@@ -343,7 +345,8 @@ TEST_F(OpCompatibilityTest, MakePolyList) {
.Input(FakeInput())
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("make_poly_list = MakePolyList[N=1, T=DT_INT32](a)", Result());
+ EXPECT_EQ("{{node make_poly_list}} = MakePolyList[N=1, T=DT_INT32](a)",
+ Result());
}
// Should be able to make a single input/output into an arbitrary list.
@@ -364,7 +367,7 @@ TEST_F(OpCompatibilityTest, MakeAnyList) {
.Input(FakeInput())
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("make_any_list = MakeAnyList[T=[DT_INT32]](a)", Result());
+ EXPECT_EQ("{{node make_any_list}} = MakeAnyList[T=[DT_INT32]](a)", Result());
}
// Should be able to make a single polymorphic input/output into a list of
@@ -387,7 +390,8 @@ TEST_F(OpCompatibilityTest, PolyIntoList) {
.Input(FakeInput(DT_INT32))
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("poly_into_list = PolyIntoList[N=1, T=DT_INT32](a)", Result());
+ EXPECT_EQ("{{node poly_into_list}} = PolyIntoList[N=1, T=DT_INT32](a)",
+ Result());
}
// Should be able to make a multiple inputs/outputs into a list with
@@ -413,7 +417,7 @@ TEST_F(OpCompatibilityTest, MakeMultipleSameList) {
.Input(FakeInput())
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("make_list = MakeMultipleSameList[N=2](a, b)", Result());
+ EXPECT_EQ("{{node make_list}} = MakeMultipleSameList[N=2](a, b)", Result());
}
// Changing from int32, float -> T
@@ -437,8 +441,9 @@ TEST_F(OpCompatibilityTest, MakeMultipleAnyList) {
.Input(FakeInput())
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("make_list = MakeMultipleAnyList[T=[DT_INT32, DT_FLOAT]](a, b)",
- Result());
+ EXPECT_EQ(
+ "{{node make_list}} = MakeMultipleAnyList[T=[DT_INT32, DT_FLOAT]](a, b)",
+ Result());
}
// Should be able to change the name of an input/output.
@@ -455,7 +460,7 @@ TEST_F(OpCompatibilityTest, ChangeName) {
.Input(FakeInput())
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("change_name = ChangeName[](a)", Result());
+ EXPECT_EQ("{{node change_name}} = ChangeName[](a)", Result());
}
// Should be able to add an input/output of type
@@ -473,7 +478,7 @@ TEST_F(OpCompatibilityTest, AddNInts) {
TF_ASSERT_OK(
NodeDefBuilder("add_n_ints", &old_op.op_def).Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("add_n_ints = AddNInts[N=0]()", Result());
+ EXPECT_EQ("{{node add_n_ints}} = AddNInts[N=0]()", Result());
}
// Should be able to add an input/output of type N * T
@@ -492,7 +497,7 @@ TEST_F(OpCompatibilityTest, AddNSame) {
TF_ASSERT_OK(
NodeDefBuilder("add_n_same", &old_op.op_def).Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("add_n_same = AddNSame[N=0, T=DT_BOOL]()", Result());
+ EXPECT_EQ("{{node add_n_same}} = AddNSame[N=0, T=DT_BOOL]()", Result());
}
// Should be able to add an input/output of type N * T
@@ -517,8 +522,10 @@ TEST_F(OpCompatibilityTest, AddNSameAsExisting) {
.Input(FakeInput(DT_STRING))
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("add_n_same_as_existing = AddNSameAsExisting[N=0, T=DT_STRING](a)",
- Result());
+ EXPECT_EQ(
+ "{{node add_n_same_as_existing}} = AddNSameAsExisting[N=0, "
+ "T=DT_STRING](a)",
+ Result());
}
// Should be able to add an input/output of type T
@@ -536,7 +543,7 @@ TEST_F(OpCompatibilityTest, AddAnyList) {
TF_ASSERT_OK(
NodeDefBuilder("add_any_list", &old_op.op_def).Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("add_any_list = AddAnyList[T=[]]()", Result());
+ EXPECT_EQ("{{node add_any_list}} = AddAnyList[T=[]]()", Result());
}
// Should be able to allow shorter lists.
@@ -557,8 +564,10 @@ TEST_F(OpCompatibilityTest, ShorterAnyList) {
.Input(FakeInput(2, DT_BOOL))
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("shorter_any_list = ShorterAnyList[T=[DT_BOOL, DT_BOOL]](a, a:1)",
- Result());
+ EXPECT_EQ(
+ "{{node shorter_any_list}} = ShorterAnyList[T=[DT_BOOL, DT_BOOL]](a, "
+ "a:1)",
+ Result());
}
REGISTER_OP("ShorterSameList")
@@ -578,7 +587,8 @@ TEST_F(OpCompatibilityTest, ShorterSameList) {
.Input(FakeInput(2))
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("shorter_same_list = ShorterSameList[N=2](a, a:1)", Result());
+ EXPECT_EQ("{{node shorter_same_list}} = ShorterSameList[N=2](a, a:1)",
+ Result());
}
// Can remove a restriction to an attr
@@ -597,7 +607,7 @@ TEST_F(OpCompatibilityTest, AttrRemoveRestriction) {
.Attr("t", DT_INT32)
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("remove_restriction = AttrRemoveRestriction[t=DT_INT32]()",
+ EXPECT_EQ("{{node remove_restriction}} = AttrRemoveRestriction[t=DT_INT32]()",
Result());
}
@@ -619,7 +629,8 @@ TEST_F(OpCompatibilityTest, AttrLessRestrictive) {
.Attr("t", DT_INT32)
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("less_restrictive = AttrLessRestrictive[t=DT_INT32]()", Result());
+ EXPECT_EQ("{{node less_restrictive}} = AttrLessRestrictive[t=DT_INT32]()",
+ Result());
}
// Can remove a minimum from an attr.
@@ -637,7 +648,7 @@ TEST_F(OpCompatibilityTest, AttrRemoveMin) {
.Attr("n", 4)
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("remove_min = AttrRemoveMin[n=4]()", Result());
+ EXPECT_EQ("{{node remove_min}} = AttrRemoveMin[n=4]()", Result());
}
// Can lower the minimum on an attr.
@@ -655,7 +666,7 @@ TEST_F(OpCompatibilityTest, AttrLowerMin) {
.Attr("n", 4)
.Finalize(node_def()));
ExpectSuccess(old_op.op_def);
- EXPECT_EQ("lower_min = AttrLowerMin[n=4]()", Result());
+ EXPECT_EQ("{{node lower_min}} = AttrLowerMin[n=4]()", Result());
}
// Can make a ref input into a non-ref input.
diff --git a/tensorflow/core/framework/op_kernel.cc b/tensorflow/core/framework/op_kernel.cc
index 507aa9e447..b53bd8d53d 100644
--- a/tensorflow/core/framework/op_kernel.cc
+++ b/tensorflow/core/framework/op_kernel.cc
@@ -1288,4 +1288,10 @@ void OpKernelContext::CtxFailureWithWarning(const char* file, int line,
SetStatus(s);
}
+void CheckNotInComputeAsync(OpKernelContext* ctx,
+ const char* correct_macro_name) {
+ CHECK_EQ(nullptr, ctx->op_kernel().AsAsync())
+ << "Use " << correct_macro_name << " in AsyncOpKernel implementations.";
+}
+
} // namespace tensorflow
diff --git a/tensorflow/core/framework/op_kernel.h b/tensorflow/core/framework/op_kernel.h
index 1fc5e9908e..2b7cc867da 100644
--- a/tensorflow/core/framework/op_kernel.h
+++ b/tensorflow/core/framework/op_kernel.h
@@ -113,6 +113,7 @@ class OpKernel {
// Returns nullptr iff this op kernel is synchronous.
virtual AsyncOpKernel* AsAsync() { return nullptr; }
+ virtual const AsyncOpKernel* AsAsync() const { return nullptr; }
// Returns true iff this op kernel is considered "expensive". The
// runtime may use this flag to optimize graph execution for example
@@ -197,6 +198,7 @@ class AsyncOpKernel : public OpKernel {
virtual void ComputeAsync(OpKernelContext* context, DoneCallback done) = 0;
AsyncOpKernel* AsAsync() final { return this; }
+ const AsyncOpKernel* AsAsync() const final { return this; }
void Compute(OpKernelContext* context) final;
@@ -1542,21 +1544,36 @@ inline void OpOutputList::set_ref(int i, mutex* mu, Tensor* tensor_for_ref) {
// ...
// }
-#define OP_REQUIRES(CTX, EXP, STATUS) \
- do { \
- if (!TF_PREDICT_TRUE(EXP)) { \
- (CTX)->CtxFailure(__FILE__, __LINE__, (STATUS)); \
- return; \
- } \
+// Generate a fatal error if OP_REQUIRES or OP_REQUIRES_OK are used in
+// AsyncOpKernel implementations. If these macros are used and the condition
+// does not hold, the `done` callback will never be called and the system will
+// deadlock, so a crash failure is preferable. Since the OP_REQUIRES[_OK] macros
+// are legal to use in AsyncOpKernel constructors, we use overload resolution
+// to distinguish between OpKernelConstruction* and OpKernelContext* context
+// types.
+class XlaOpKernelContext;
+inline void CheckNotInComputeAsync(XlaOpKernelContext*, const char*) {}
+inline void CheckNotInComputeAsync(OpKernelConstruction*, const char*) {}
+void CheckNotInComputeAsync(OpKernelContext* ctx,
+ const char* correct_macro_name);
+
+#define OP_REQUIRES(CTX, EXP, STATUS) \
+ do { \
+ if (!TF_PREDICT_TRUE(EXP)) { \
+ CheckNotInComputeAsync((CTX), "OP_REQUIRES_ASYNC"); \
+ (CTX)->CtxFailure(__FILE__, __LINE__, (STATUS)); \
+ return; \
+ } \
} while (0)
-#define OP_REQUIRES_OK(CTX, ...) \
- do { \
- ::tensorflow::Status _s(__VA_ARGS__); \
- if (!TF_PREDICT_TRUE(_s.ok())) { \
- (CTX)->CtxFailureWithWarning(__FILE__, __LINE__, _s); \
- return; \
- } \
+#define OP_REQUIRES_OK(CTX, ...) \
+ do { \
+ ::tensorflow::Status _s(__VA_ARGS__); \
+ if (!TF_PREDICT_TRUE(_s.ok())) { \
+ CheckNotInComputeAsync((CTX), "OP_REQUIRES_OK_ASYNC"); \
+ (CTX)->CtxFailureWithWarning(__FILE__, __LINE__, _s); \
+ return; \
+ } \
} while (0)
#define OP_REQUIRES_ASYNC(CTX, EXP, STATUS, CALLBACK) \
diff --git a/tensorflow/core/framework/tensor_testutil.cc b/tensorflow/core/framework/tensor_testutil.cc
index 8f480d65f2..1a7812ce4e 100644
--- a/tensorflow/core/framework/tensor_testutil.cc
+++ b/tensorflow/core/framework/tensor_testutil.cc
@@ -20,30 +20,42 @@ namespace tensorflow {
namespace test {
template <typename T>
-bool IsClose(const T& x, const T& y, double atol, double rtol) {
- // Need x == y so that infinities are close to themselves
- return x == y || std::abs(x - y) < atol + rtol * std::abs(x);
-}
-
-template <typename T>
void ExpectClose(const Tensor& x, const Tensor& y, double atol, double rtol) {
- auto Tx = x.flat<T>();
- auto Ty = y.flat<T>();
- for (int i = 0; i < Tx.size(); ++i) {
- if (!IsClose(Tx(i), Ty(i), atol, rtol)) {
- LOG(ERROR) << "x = " << x.DebugString();
- LOG(ERROR) << "y = " << y.DebugString();
- LOG(ERROR) << "atol = " << atol << " rtol = " << rtol
- << " tol = " << atol + rtol * std::abs(Tx(i));
- EXPECT_TRUE(false) << i << "-th element is not close " << Tx(i) << " vs. "
- << Ty(i);
- }
+ const T* Tx = x.flat<T>().data();
+ const T* Ty = y.flat<T>().data();
+ const auto size = x.NumElements();
+
+ // Tolerance's type (RealType) can be different from T.
+ // For example, if T = std::complex<float>, then RealType = float.
+ // Did not use std::numeric_limits<T> because
+ // 1) It returns 0 for Eigen::half.
+ // 2) It doesn't support T=std::complex<RealType>.
+ // (Would have to write a templated struct to handle this.)
+ typedef decltype(Eigen::NumTraits<T>::epsilon()) RealType;
+ const RealType kSlackFactor = static_cast<RealType>(5.0);
+ const RealType kDefaultTol = kSlackFactor * Eigen::NumTraits<T>::epsilon();
+ const RealType typed_atol =
+ (atol < 0) ? kDefaultTol : static_cast<RealType>(atol);
+ const RealType typed_rtol =
+ (rtol < 0) ? kDefaultTol : static_cast<RealType>(rtol);
+ ASSERT_GE(typed_atol, static_cast<RealType>(0.0))
+ << "typed_atol is negative: " << typed_atol;
+ ASSERT_GE(typed_rtol, static_cast<RealType>(0.0))
+ << "typed_rtol is negative: " << typed_rtol;
+ for (int i = 0; i < size; ++i) {
+ EXPECT_TRUE(
+ internal::Helper<T>::IsClose(Tx[i], Ty[i], typed_atol, typed_rtol))
+ << "index = " << i << " x = " << Tx[i] << " y = " << Ty[i]
+ << " typed_atol = " << typed_atol << " typed_rtol = " << typed_rtol;
}
}
void ExpectClose(const Tensor& x, const Tensor& y, double atol, double rtol) {
internal::AssertSameTypeDims(x, y);
switch (x.dtype()) {
+ case DT_HALF:
+ ExpectClose<Eigen::half>(x, y, atol, rtol);
+ break;
case DT_FLOAT:
ExpectClose<float>(x, y, atol, rtol);
break;
diff --git a/tensorflow/core/framework/tensor_testutil.h b/tensorflow/core/framework/tensor_testutil.h
index 4c216a84f0..3163002851 100644
--- a/tensorflow/core/framework/tensor_testutil.h
+++ b/tensorflow/core/framework/tensor_testutil.h
@@ -13,8 +13,8 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
-#ifndef TENSORFLOW_FRAMEWORK_TENSOR_TESTUTIL_H_
-#define TENSORFLOW_FRAMEWORK_TENSOR_TESTUTIL_H_
+#ifndef TENSORFLOW_CORE_FRAMEWORK_TENSOR_TESTUTIL_H_
+#define TENSORFLOW_CORE_FRAMEWORK_TENSOR_TESTUTIL_H_
#include <numeric>
@@ -105,9 +105,10 @@ void ExpectTensorNear(const Tensor& x, const Tensor& y, const T& abs_err);
// Expects "x" and "y" are tensors of the same type (float or double),
// same shape and element-wise difference between x and y is no more
-// than atol + rtol * abs(x).
-void ExpectClose(const Tensor& x, const Tensor& y, double atol = 1e-6,
- double rtol = 1e-6);
+// than atol + rtol * abs(x). If atol or rtol is negative, it is replaced
+// with a default tolerance value = data type's epsilon * kSlackFactor.
+void ExpectClose(const Tensor& x, const Tensor& y, double atol = -1.0,
+ double rtol = -1.0);
// Implementation details.
@@ -191,11 +192,10 @@ struct Expector<T, true> {
}
}
- static void Near(const T& a, const T& b, const double abs_err, int index) {
- if (a != b) { // Takes care of inf.
- EXPECT_LE(double(Eigen::numext::abs(a - b)), abs_err)
- << "a = " << a << " b = " << b << " index = " << index;
- }
+ static bool Near(const T& a, const T& b, const double abs_err) {
+ // Need a == b so that infinities are close to themselves.
+ return (a == b) ||
+ (static_cast<double>(Eigen::numext::abs(a - b)) <= abs_err);
}
static void Near(const Tensor& x, const Tensor& y, const double abs_err) {
@@ -205,11 +205,31 @@ struct Expector<T, true> {
const T* a = x.flat<T>().data();
const T* b = y.flat<T>().data();
for (int i = 0; i < size; ++i) {
- Near(a[i], b[i], abs_err, i);
+ EXPECT_TRUE(Near(a[i], b[i], abs_err))
+ << "a = " << a[i] << " b = " << b << " index = " << i;
}
}
};
+template <typename T>
+struct Helper {
+ // Assumes atol and rtol are nonnegative.
+ static bool IsClose(const T& x, const T& y, const T& atol, const T& rtol) {
+ // Need x == y so that infinities are close to themselves.
+ return (x == y) ||
+ (Eigen::numext::abs(x - y) <= atol + rtol * Eigen::numext::abs(x));
+ }
+};
+
+template <typename T>
+struct Helper<std::complex<T>> {
+ static bool IsClose(const std::complex<T>& x, const std::complex<T>& y,
+ const T& atol, const T& rtol) {
+ return Helper<T>::IsClose(x.real(), y.real(), atol, rtol) &&
+ Helper<T>::IsClose(x.imag(), y.imag(), atol, rtol);
+ }
+};
+
} // namespace internal
template <typename T>
@@ -221,10 +241,11 @@ template <typename T>
void ExpectTensorNear(const Tensor& x, const Tensor& y, const double abs_err) {
static_assert(internal::is_floating_point_type<T>::value,
"T is not a floating point types.");
+ ASSERT_GE(abs_err, 0.0) << "abs_error is negative" << abs_err;
internal::Expector<T>::Near(x, y, abs_err);
}
} // namespace test
} // namespace tensorflow
-#endif // TENSORFLOW_FRAMEWORK_TENSOR_TESTUTIL_H_
+#endif // TENSORFLOW_CORE_FRAMEWORK_TENSOR_TESTUTIL_H_
diff --git a/tensorflow/core/framework/tensor_testutil_test.cc b/tensorflow/core/framework/tensor_testutil_test.cc
new file mode 100644
index 0000000000..8dca25ac4c
--- /dev/null
+++ b/tensorflow/core/framework/tensor_testutil_test.cc
@@ -0,0 +1,357 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/core/framework/tensor_testutil.h"
+
+#include "tensorflow/core/platform/test.h"
+#include "tensorflow/core/util/ptr_util.h"
+
+namespace tensorflow {
+namespace test {
+namespace {
+
+using internal::Expector;
+using internal::Helper;
+
+template <typename T>
+static void TestEdgeCasesNear() {
+ EXPECT_TRUE(Expector<T>::Near(Eigen::NumTraits<T>::infinity(),
+ Eigen::NumTraits<T>::infinity(), 0.0));
+ EXPECT_TRUE(Expector<T>::Near(Eigen::NumTraits<T>::lowest(),
+ Eigen::NumTraits<T>::highest(),
+ Eigen::NumTraits<double>::infinity()));
+ EXPECT_FALSE(Expector<T>::Near(Eigen::NumTraits<T>::lowest(),
+ Eigen::NumTraits<T>::highest(),
+ Eigen::NumTraits<double>::highest()));
+ EXPECT_FALSE(Expector<T>::Near(Eigen::NumTraits<T>::quiet_NaN(),
+ Eigen::NumTraits<T>::quiet_NaN(), 0.0));
+ EXPECT_FALSE(Expector<T>::Near(Eigen::NumTraits<T>::quiet_NaN(),
+ Eigen::NumTraits<T>::quiet_NaN(),
+ Eigen::NumTraits<double>::infinity()));
+}
+
+// For debug printing. Example usage:
+// dumpFloatingPointStorage<Eigen::half, uint16>(
+// static_cast<Eigen::half>(-2.71f));
+// dumpFloatingPointStorage<float, uint32>(-2.718281f);
+// dumpFloatingPointStorage <double, uint64>(-2.71828182846);
+template <typename T, typename U>
+static void dumpFloatingPointStorage(T value) {
+ U* integral = reinterpret_cast<U*>(&value);
+ int shift_amount = (sizeof(U) << 3) - 1;
+ int exponent_bits = 2 + (log2(sizeof(U)) * 3);
+ U mask = static_cast<U>(1) << shift_amount;
+ for (int bits = 0; bits <= shift_amount; ++bits) {
+ std::cout << ((*integral & mask) > 0);
+ if (bits == 0 || bits == exponent_bits) std::cout << " ";
+ mask >>= 1;
+ }
+ std::cout << std::endl;
+ printf("%.20lf\n", static_cast<double>(value));
+}
+
+TEST(TensorTestUtilTest, ExpectTensorNearHalf) {
+ // Eigen::half has 1 sign bit, 5 exponent bits, and 10 mantissa bits.
+ // The exponent is offset at 15.
+ // https://en.wikipedia.org/wiki/Half-precision_floating-point_format
+ typedef Eigen::half T;
+#define HALF(x) static_cast<T>(x)
+
+ // Trivial cases: equalities.
+ EXPECT_TRUE(Expector<T>::Near(HALF(1.0f), HALF(1.0f), 0.0));
+ EXPECT_TRUE(Expector<T>::Near(HALF(0.0f), HALF(-0.0f), 0.0));
+ EXPECT_TRUE(Expector<T>::Near(HALF(3.141592f), HALF(3.141592f), 0.0));
+
+ // 0 10010 0001111110 -> 1150/128 = 8.984375 vs
+ // 0 10010 0001111111 -> 1151/128 = 8.9921875 (diff = 0.0078125)
+ EXPECT_TRUE(Expector<T>::Near(HALF(8.9875f), HALF(8.99f), 0.0078125));
+ EXPECT_FALSE(Expector<T>::Near(HALF(8.9875f), HALF(8.99f), 0.007));
+
+ // 0 11000 0110100000 -> 1440/2 = 720 vs
+ // 0 11000 0110100001 -> 1441/2 = 720.5 (diff = 0.5)
+ EXPECT_TRUE(Expector<T>::Near(HALF(720.2f), HALF(720.3f), 0.5));
+ EXPECT_FALSE(Expector<T>::Near(HALF(720.2f), HALF(720.3f), 0.4));
+
+ // 0 11001 0011010010 -> 1234 vs
+ // 0 11001 0011010011 -> 1235 (diff = 1)
+ // Rounds to even (1234.5 -> 1234).
+ EXPECT_TRUE(Expector<T>::Near(HALF(1234.f), HALF(1235.f), 1.0));
+ EXPECT_FALSE(Expector<T>::Near(HALF(1234.5f), HALF(1235.f), 0.5));
+ EXPECT_TRUE(Expector<T>::Near(HALF(1234.5f), HALF(1235.f), 1.0));
+
+ // 1 10000 0101101100 -> -1388/512 = -2.7109375 vs
+ // 1 10000 0101110001 -> -1393/512 = -2.720703125 (diff = 0.009765625)
+ EXPECT_TRUE(Expector<T>::Near(HALF(-2.71f), HALF(-2.72f), 0.01));
+
+#undef HALF
+
+ // Some of the cases failed because Eigen::half doesn't behave as expected.
+ // For example, (inf == inf) should have been true, but it returns false.
+ // TODO(penporn): uncomment this test once we fix Eigen::half
+ // TestEdgeCasesNear<T>();
+}
+
+TEST(TensorTestUtilTest, ExpectTensorNearFloat) {
+ // float has 1 sign bit, 8 exponent bits, and 23 mantissa bits.
+ // The exponent offset is 127.
+ // https://en.wikipedia.org/wiki/Single-precision_floating-point_format
+ typedef float T;
+ // Trivial cases: equalities.
+ EXPECT_TRUE(Expector<T>::Near(1.0f, 1.0f, 0.0));
+ EXPECT_TRUE(Expector<T>::Near(0.0f, -0.0f, 0.0));
+ EXPECT_TRUE(Expector<T>::Near(3.14159265359f, 3.14159265359f, 0.0));
+
+ // 0 10000010 00011111100110011001101 -> 9,424,077/2^20 vs
+ // 0 10000010 00011111100110100110110 -> 9,424,182/2^20
+ // diff = 105/2^20 = 0.000100135803223
+ EXPECT_TRUE(Expector<T>::Near(8.9875f, 8.9876f, 0.0001002));
+ EXPECT_FALSE(Expector<T>::Near(8.9875f, 8.9876f, 0.0001));
+
+ // 0 10001000 01101000000110011101001 -> 11,799,785/2^14 vs
+ // 0 10001000 01101000000110011101010 -> 11,799,786/2^14
+ // diff = 1/2^14 = 0.00006103515625
+ EXPECT_TRUE(Expector<T>::Near(720.2017f, 720.2018f, 0.0001));
+ EXPECT_FALSE(Expector<T>::Near(720.20175f, 720.20185f, 0.0001));
+ EXPECT_TRUE(Expector<T>::Near(720.20175f, 720.20185f, 0.00013));
+
+ // 0 10011001 11010110111100110100010 -> 15,432,098*2^3 vs
+ // 0 10011001 11010110111100110100011 -> 15,432,099*2^3 (diff = 2^3 = 8)
+ EXPECT_FALSE(Expector<T>::Near(123456788.f, 123456789.f, 4.0));
+ EXPECT_TRUE(Expector<T>::Near(123456788.f, 123456789.f, 8.0));
+
+ // 1 10000000 01011011111100001010001 -> 11,401,297/2^22 vs
+ // 1 10000000 01011011111100001010101 -> 11,401,301/2^22
+ // diff = 4/2^22 = 0.000000953674316
+ EXPECT_TRUE(Expector<T>::Near(-2.718281f, -2.718282f, 0.1));
+
+ TestEdgeCasesNear<T>();
+}
+
+TEST(TensorTestUtilTest, ExpectTensorNearDouble) {
+ // double has 1 sign bit, 11 exponent bits, and 52 mantissa bits.
+ // The exponent offset is 1,023.
+ // https://en.wikipedia.org/wiki/Double-precision_floating-point_format
+ typedef double T;
+ // Trivial cases: equalities.
+ EXPECT_TRUE(Expector<T>::Near(1.0, 1.0, 0.0));
+ EXPECT_TRUE(Expector<T>::Near(0.0, -0.0, 0.0));
+ EXPECT_TRUE(Expector<T>::Near(3.14159265359, 3.14159265359, 0.0));
+
+ // 0 10000000010 0001111110011001100110011001100110011001100110011010
+ // -> 5,059,512,706,374,042/2^49 vs
+ // 0 10000000010 0001111110011010011010110101000010110000111100101000
+ // -> 5,059,569,001,369,384/2^49
+ // diff = 56,294,995,342/2^49 = 9.999999999976694198267E-5
+ EXPECT_TRUE(Expector<T>::Near(8.9875, 8.9876, 0.0001));
+
+ // 0 10000001111 1000100101110000001100111010100100101010001100000101
+ // -> 6,921,439,564,440,325/2^36
+ // 0 10000001111 1000100101110000001100111010111110110111111010010001
+ // -> 6,921,439,571,312,273/2^36
+ // diff = 6,871,948/2^36 = 1.000000047497451305389E-4
+ EXPECT_FALSE(Expector<T>::Near(100720.2018, 100720.2019, 0.0001));
+ EXPECT_TRUE(Expector<T>::Near(100720.2018, 100720.2019, 1.00000005e-4));
+
+ // 0 10000110100 0101111011100010101000101110101101011010010111000100
+ // -> 6,172,839,450,617,284 * 2
+ // 0 10000110100 0101111011100010101000101110101101011010010111000011
+ // -> 6,172,839,450,617,283 * 2
+ // diff = 1 * 2 = 2
+ EXPECT_FALSE(Expector<T>::Near(12345678901234567., 12345678901234566., 1.0));
+ EXPECT_TRUE(Expector<T>::Near(12345678901234567., 12345678901234566., 2.0));
+
+ // 1 10000000000 0101101111110000101010001011000101000101111111001111
+ // -> -6,121,026,514,870,223/2^51
+ // 1 10000000000 0101101111110000101010001011000101001011011111000101
+ // -> -6,121,026,514,892,741/2^51
+ // diff = 22,518/2^51 = 1.00000008274037099909E-11
+ EXPECT_FALSE(Expector<T>::Near(-2.71828182846, -2.71828182847, 1.0e-11));
+ EXPECT_TRUE(
+ Expector<T>::Near(-2.71828182846, -2.71828182847, 1.00000009e-11));
+
+ TestEdgeCasesNear<T>();
+}
+
+static const double kSlackFactor = 5.0;
+
+template <typename T>
+static void TestEdgeCasesClose() {
+ T kZero = static_cast<T>(0.0);
+ EXPECT_TRUE(Helper<T>::IsClose(Eigen::NumTraits<T>::infinity(),
+ Eigen::NumTraits<T>::infinity(), kZero,
+ kZero));
+ EXPECT_TRUE(Helper<T>::IsClose(
+ Eigen::NumTraits<T>::lowest(), Eigen::NumTraits<T>::highest(),
+ Eigen::NumTraits<T>::infinity(), Eigen::NumTraits<T>::infinity()));
+ EXPECT_TRUE(Helper<T>::IsClose(
+ Eigen::NumTraits<T>::lowest(), Eigen::NumTraits<T>::highest(),
+ Eigen::NumTraits<T>::highest(), Eigen::NumTraits<T>::highest()));
+ EXPECT_FALSE(Helper<T>::IsClose(Eigen::NumTraits<T>::quiet_NaN(),
+ Eigen::NumTraits<T>::quiet_NaN(), kZero,
+ kZero));
+ EXPECT_FALSE(Helper<T>::IsClose(
+ Eigen::NumTraits<T>::quiet_NaN(), Eigen::NumTraits<T>::quiet_NaN(),
+ Eigen::NumTraits<T>::infinity(), Eigen::NumTraits<T>::infinity()));
+}
+
+TEST(TensorTestUtilTest, ExpectTensorCloseHalf) {
+ typedef Eigen::half T;
+#define HALF(x) static_cast<T>(x)
+ EXPECT_TRUE(
+ Helper<T>::IsClose(HALF(1.0f), HALF(1.1f), HALF(0.1f), HALF(0.1f)));
+ EXPECT_TRUE(
+ Helper<T>::IsClose(HALF(1.0f), HALF(1.0f), HALF(0.0f), HALF(0.0f)));
+ EXPECT_FALSE(
+ Helper<T>::IsClose(HALF(1.0f), HALF(1.1f), HALF(0.0f), HALF(0.0f)));
+
+ // Epsilon: 0 00010 0000000000 -> 2^-13 = 0.0001220703125
+ // kDefaultTol: 0 00100 0100000000 -> 5/2^13 = 0.0006103515625
+ const T kDefaultTol =
+ static_cast<T>(kSlackFactor) * Eigen::NumTraits<T>::epsilon();
+
+ // 1.234 -> 0 01111 0011110000 -> 1264/2^10 = 1.234375
+ // 1.233 -> 0 01111 0011101111 -> 1263/2^10 = 1.2333984375
+ // 1.235 -> 0 01111 0011110001 -> 1265/2^10 = 1.2353515625
+ // 1.232 -> 0 01111 0011101110 -> 1262/2^10 = 1.232421875
+ // 1.236 -> 0 01111 0011110010 -> 1266/2^10 = 1.236328125
+ // 1/2^10 = 0.0009765625E
+ // Threshold = 0.0013637542724609375
+ EXPECT_TRUE(
+ Helper<T>::IsClose(HALF(1.234f), HALF(1.234f), kDefaultTol, kDefaultTol));
+ EXPECT_TRUE(
+ Helper<T>::IsClose(HALF(1.234f), HALF(1.233f), kDefaultTol, kDefaultTol));
+ EXPECT_TRUE(
+ Helper<T>::IsClose(HALF(1.234f), HALF(1.235f), kDefaultTol, kDefaultTol));
+
+ // Diff = 0.001953125
+ EXPECT_FALSE(
+ Helper<T>::IsClose(HALF(1.234f), HALF(1.232f), kDefaultTol, kDefaultTol));
+ EXPECT_FALSE(
+ Helper<T>::IsClose(HALF(1.234f), HALF(1.236f), kDefaultTol, kDefaultTol));
+ EXPECT_TRUE(
+ Helper<T>::IsClose(HALF(1.234f), HALF(1.232f), HALF(8e-4f), HALF(1e-3f)));
+ EXPECT_TRUE(Helper<T>::IsClose(HALF(1.234f), HALF(1.236f), HALF(1.4e-3f),
+ HALF(5e-4f)));
+
+ // Too fine-grained: won't detect the difference
+ EXPECT_TRUE(Helper<T>::IsClose(HALF(3.141592f), HALF(3.141593f), HALF(0.0),
+ HALF(0.0)));
+
+ // This case failed because HALF(1e7f) is stored as inf, which it shouldn't.
+ // TODO(penporn): Debug Eigen::half and uncomment this test case.
+ // EXPECT_FALSE(
+ // Helper<T>::IsClose(HALF(1e7f), HALF(1e-7f), kDefaultTol, kDefaultTol));
+#undef HALF
+
+ // Some of the cases failed because Eigen::half doesn't behave as expected.
+ // For example, (inf == inf) should have been true, but it returns false.
+ // TODO(penporn): uncomment this test once we fix Eigen::half
+ // TestEdgeCasesClose<T>();
+}
+
+TEST(TensorTestUtilTest, ExpectTensorCloseFloat) {
+ typedef float T;
+
+ EXPECT_TRUE(Helper<T>::IsClose(1.0f, 1.1f, 0.1f, 0.1f));
+ EXPECT_TRUE(Helper<T>::IsClose(1.0f, 1.0f, 0.0f, 0.0f));
+ EXPECT_FALSE(Helper<T>::IsClose(1.0f, 1.1f, 0.0f, 0.0f));
+
+ // Epsilon: 2^-23 ~ 0.00000011920928955078
+ // kDefaultTol: 5/2^23 ~ 0.00000059604644775391
+ const T kDefaultTol =
+ static_cast<T>(kSlackFactor) * Eigen::NumTraits<T>::epsilon();
+
+ // 1.234567f -> 10,356,299/2^23 ~ 1.234567046165466308594
+ // 1.234568f -> 10,356,307/2^23 ~ 1.234567999839782714844
+ // 1.234566f -> 10,356,290/2^23 ~ 1.234565973281860351563
+ // 1.234569f -> 10,356,315/2^23 ~ 1.234568953514099121094
+ // 1.234565f -> 10,356,282/2^23 ~ 1.234565019607543945313
+ // Threshold ~ 0.00000133190576434572
+ EXPECT_TRUE(
+ Helper<T>::IsClose(1.234567f, 1.234567f, kDefaultTol, kDefaultTol));
+ EXPECT_TRUE(
+ Helper<T>::IsClose(1.234567f, 1.234568f, kDefaultTol, kDefaultTol));
+ EXPECT_TRUE(
+ Helper<T>::IsClose(1.234567f, 1.234566f, kDefaultTol, kDefaultTol));
+ EXPECT_FALSE(
+ Helper<T>::IsClose(1.234567f, 1.234569f, kDefaultTol, kDefaultTol));
+ EXPECT_FALSE(
+ Helper<T>::IsClose(1.234567f, 1.234565f, kDefaultTol, kDefaultTol));
+ EXPECT_TRUE(Helper<T>::IsClose(1.234567f, 1.234569f, 8e-7f, 1e-6f));
+ EXPECT_TRUE(Helper<T>::IsClose(1.234567f, 1.234565f, 3e-7f, 1.5e-6f));
+
+ // Too fine-grained: won't detect the difference
+ EXPECT_TRUE(Helper<T>::IsClose(3.14159265f, 3.14159266f, 0.0f, 0.0f));
+
+ // Trivial cases
+ EXPECT_FALSE(Helper<T>::IsClose(1e8f, 1e-8f, kDefaultTol, kDefaultTol));
+ EXPECT_FALSE(Helper<T>::IsClose(1e15f, 1e-15f, kDefaultTol, kDefaultTol));
+
+ TestEdgeCasesClose<T>();
+}
+
+TEST(TensorTestUtilTest, ExpectTensorCloseDouble) {
+ typedef double T;
+
+ EXPECT_TRUE(Helper<T>::IsClose(1.0, 1.1, 0.1, 0.1));
+ EXPECT_TRUE(Helper<T>::IsClose(1.0, 1.0, 0.0, 0.0));
+ EXPECT_FALSE(Helper<T>::IsClose(1.0, 1.1, 0.0, 0.0));
+
+ // Epsilon: 2^-52 ~ 2.220446049250313080847E-16
+ // kDefaultTol: 5/2^52 ~ 1.110223024625156540424E-15
+ const T kDefaultTol =
+ static_cast<T>(kSlackFactor) * Eigen::NumTraits<T>::epsilon();
+
+ // 1.234567890123456 -> 5,559,999,489,923,576/2^52 ~ 1.234567890123456024298
+ // 1.234567890123457 -> 5,559,999,489,923,580/2^52 ~ 1.234567890123456912477
+ // 1.234567890123455 -> 5,559,999,489,923,571/2^52 ~ 1.234567890123454914075
+ // 1.234567890123458 -> 5,559,999,489,923,585/2^52 ~ 1.2345678901234580227
+ // 1.234567890123454 -> 5,559,999,489,923,567/2^52 ~ 1.234567890123454025897
+ // 1.234567890123459 -> 5,559,999,489,923,589/2^52 ~ 1.234567890123458910878
+ // 1.234567890123453 -> 5,559,999,489,923,562/2^52 ~ 1.234567890123452915674
+ // Threshold ~ 2.480868721703117812159E-15
+ EXPECT_TRUE(Helper<T>::IsClose(1.234567890123456, 1.234567890123456,
+ kDefaultTol, kDefaultTol));
+ EXPECT_TRUE(Helper<T>::IsClose(1.234567890123456, 1.234567890123457,
+ kDefaultTol, kDefaultTol));
+ EXPECT_TRUE(Helper<T>::IsClose(1.234567890123456, 1.234567890123455,
+ kDefaultTol, kDefaultTol));
+ EXPECT_TRUE(Helper<T>::IsClose(1.234567890123456, 1.234567890123458,
+ kDefaultTol, kDefaultTol));
+ EXPECT_TRUE(Helper<T>::IsClose(1.234567890123456, 1.234567890123454,
+ kDefaultTol, kDefaultTol));
+ EXPECT_FALSE(Helper<T>::IsClose(1.234567890123456, 1.234567890123459,
+ kDefaultTol, kDefaultTol));
+ EXPECT_FALSE(Helper<T>::IsClose(1.234567890123456, 1.234567890123453,
+ kDefaultTol, kDefaultTol));
+ EXPECT_TRUE(Helper<T>::IsClose(1.234567890123456, 1.234567890123459, 9.5e-16,
+ 1.6e-15));
+ EXPECT_TRUE(
+ Helper<T>::IsClose(1.234567890123456, 1.234567890123453, 7e-16, 2e-15));
+
+ // Too fine-grained: won't detect the difference
+ EXPECT_TRUE(
+ Helper<T>::IsClose(3.141592653589793238, 3.141592653589793239, 0.0, 0.0));
+
+ // Trivial cases
+ EXPECT_FALSE(Helper<T>::IsClose(1e15, 1e-15, kDefaultTol, kDefaultTol));
+ EXPECT_FALSE(Helper<T>::IsClose(1e30, 1e-30, kDefaultTol, kDefaultTol));
+
+ TestEdgeCasesClose<T>();
+}
+
+} // namespace
+} // namespace test
+} // namespace tensorflow
diff --git a/tensorflow/core/graph/control_flow.cc b/tensorflow/core/graph/control_flow.cc
index 1778e48ef6..8e1e56d29b 100644
--- a/tensorflow/core/graph/control_flow.cc
+++ b/tensorflow/core/graph/control_flow.cc
@@ -18,6 +18,7 @@ limitations under the License.
#include <deque>
#include <vector>
+#include "tensorflow/core/framework/node_def_util.h"
#include "tensorflow/core/framework/types.h"
#include "tensorflow/core/graph/node_builder.h"
#include "tensorflow/core/lib/core/errors.h"
@@ -54,10 +55,11 @@ Status ValidateControlFlowInfo(const Graph* graph,
frame.parent = parent;
frame.name = cf.frame_name;
} else if (frame.parent != parent) {
- return errors::InvalidArgument(
+ return errors::Internal(
"Invalid loop structure: Mismatched parent frames for \"",
cf.frame_name, "\": \"", parent->name, "\" vs \"", frame.parent->name,
- "\". This is an internal bug, please file a bug report with "
+ "\". The node giving this error: ", FormatNodeForError(*node),
+ "This is an internal bug, please file a bug report with "
"instructions on how to reproduce the error.");
}
if (IsLoopCond(node)) {
@@ -69,9 +71,9 @@ Status ValidateControlFlowInfo(const Graph* graph,
!str_util::StrContains(node->name(), "LoopCounter")) {
return errors::InvalidArgument(
"Invalid loop structure: Loop \"", cf.frame_name,
- "\" has more than one LoopCond node: \"", node->name(), "\" and \"",
- frame.loop_cond->name(),
- "\". This is an internal bug, please file a bug report with "
+ "\" has more than one LoopCond node: ", FormatNodeForError(*node),
+ " and ", FormatNodeForError(*frame.loop_cond),
+ ". This is an internal bug, please file a bug report with "
"instructions on how to reproduce the error.");
}
frame.loop_cond = node;
@@ -135,12 +137,11 @@ Status BuildControlFlowInfo(const Graph* g, std::vector<ControlFlowInfo>* info,
const string& parent_frame = (*info)[out_parent->id()].frame_name;
if (parent_frame != frame_name) {
return errors::InvalidArgument(
- "The node '", out->name(),
- "' has inputs from different "
- "frames. The input '",
- curr_node->name(), "' is in frame '", frame_name,
- "'. The input '", parent_nodes[out->id()]->name(),
- "' is in frame '", parent_frame, "'.");
+ FormatNodeForError(*out),
+ " has inputs from different frames. The input ",
+ FormatNodeForError(*curr_node), " is in frame '", frame_name,
+ "'. The input ", FormatNodeForError(*parent_nodes[out->id()]),
+ " is in frame '", parent_frame, "'.");
}
} else {
out_info->frame = out;
@@ -148,7 +149,8 @@ Status BuildControlFlowInfo(const Graph* g, std::vector<ControlFlowInfo>* info,
TF_RETURN_IF_ERROR(
GetNodeAttr(out->attrs(), "frame_name", &out_info->frame_name));
if (out_info->frame_name.empty()) {
- return errors::InvalidArgument("The Enter node ", out->name(),
+ return errors::InvalidArgument("The Enter ",
+ FormatNodeForError(*out),
" must have a frame name.");
}
}
@@ -156,12 +158,11 @@ Status BuildControlFlowInfo(const Graph* g, std::vector<ControlFlowInfo>* info,
if (is_visited) {
if (out_info->frame_name != frame_name) {
return errors::InvalidArgument(
- "The node '", out->name(),
- "' has inputs from different "
- "frames. The input '",
- curr_node->name(), "' is in frame '", frame_name,
- "'. The input '", parent_nodes[out->id()]->name(),
- "' is in frame '", out_info->frame_name, "'.");
+ FormatNodeForError(*out),
+ " has inputs from different frames. The input ",
+ FormatNodeForError(*curr_node), " is in frame '", frame_name,
+ "'. The input ", FormatNodeForError(*parent_nodes[out->id()]),
+ " is in frame '", out_info->frame_name, "'.");
}
} else {
out_info->frame = frame;
diff --git a/tensorflow/core/graph/control_flow_test.cc b/tensorflow/core/graph/control_flow_test.cc
index eb7937400f..803c757c3f 100644
--- a/tensorflow/core/graph/control_flow_test.cc
+++ b/tensorflow/core/graph/control_flow_test.cc
@@ -63,6 +63,15 @@ TEST(ValidateControlFlowTest, InputsFromDifferentFrames) {
EXPECT_TRUE(str_util::StrContains(status.error_message(),
"has inputs from different frames"))
<< status.error_message();
+ EXPECT_TRUE(str_util::StrContains(status.error_message(),
+ "{{node outer/body/inner/Merge}}"))
+ << status.error_message();
+ EXPECT_TRUE(str_util::StrContains(status.error_message(),
+ "{{node outer/body/inner/Enter}}"))
+ << status.error_message();
+ EXPECT_TRUE(
+ str_util::StrContains(status.error_message(), "{{node outer/Switch}}"))
+ << status.error_message();
}
TEST(ValidateControlFlowTest, MismatchedParentFrames) {
@@ -102,6 +111,8 @@ TEST(ValidateControlFlowTest, MismatchedParentFrames) {
EXPECT_TRUE(
str_util::StrContains(status.error_message(), "Mismatched parent frames"))
<< status.error_message();
+ EXPECT_TRUE(str_util::StrContains(status.error_message(), "{{node Enter2}}"))
+ << status.error_message();
}
TEST(ValidateControlFlowTest, TwoLoopCond) {
@@ -125,6 +136,12 @@ TEST(ValidateControlFlowTest, TwoLoopCond) {
EXPECT_TRUE(str_util::StrContains(status.error_message(),
"more than one LoopCond node"))
<< status.error_message();
+ EXPECT_TRUE(
+ str_util::StrContains(status.error_message(), "{{node sub/LoopCond}}"))
+ << status.error_message();
+ EXPECT_TRUE(
+ str_util::StrContains(status.error_message(), "{{node LoopCond}}"))
+ << status.error_message();
}
} // namespace
diff --git a/tensorflow/core/graph/graph_partition.cc b/tensorflow/core/graph/graph_partition.cc
index 1b1941f9c1..ea0a814ab8 100644
--- a/tensorflow/core/graph/graph_partition.cc
+++ b/tensorflow/core/graph/graph_partition.cc
@@ -214,6 +214,14 @@ NodeDef* AddSend(const PartitionOptions& opts, const GraphInfo& g_info,
cast_builder.Attr("_start_time", start_time);
}
cast_builder.Attr("DstT", cast_dtype);
+
+ if (cast_dtype == DT_BFLOAT16) {
+ // the below attribute specifies that the cast to bfloat16 should use
+ // truncation. This is needed to retain legacy behavior when we change
+ // the default bfloat16 casts to use rounding instead of truncation
+ cast_builder.Attr("Truncate", true);
+ }
+
NodeDef* cast = gdef->add_node();
*status = cast_builder.Finalize(cast);
if (!status->ok()) return nullptr;
diff --git a/tensorflow/core/graph/mkl_graph_util.h b/tensorflow/core/graph/mkl_graph_util.h
index 5f51d6083b..333bf761b0 100644
--- a/tensorflow/core/graph/mkl_graph_util.h
+++ b/tensorflow/core/graph/mkl_graph_util.h
@@ -17,7 +17,6 @@ limitations under the License.
#define TENSORFLOW_CORE_GRAPH_MKL_GRAPH_UTIL_H_
#ifdef INTEL_MKL
-#include <string>
#include "tensorflow/core/framework/op_kernel.h"
namespace tensorflow {
diff --git a/tensorflow/core/graph/mkl_layout_pass.cc b/tensorflow/core/graph/mkl_layout_pass.cc
index b9667998d6..c22e0a3872 100644
--- a/tensorflow/core/graph/mkl_layout_pass.cc
+++ b/tensorflow/core/graph/mkl_layout_pass.cc
@@ -22,7 +22,6 @@ limitations under the License.
#include <memory>
#include <queue>
#include <set>
-#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -2495,13 +2494,13 @@ class MklLayoutRewritePass : public GraphOptimizationPass {
CopyAttrsLRN, LrnRewrite});
rinfo_.push_back({csinfo_.lrn_grad,
mkl_op_registry::GetMklOpName(csinfo_.lrn_grad),
- CopyAttrsLRN, LrnRewrite});
+ CopyAttrsLRN, LrnGradRewrite});
rinfo_.push_back({csinfo_.max_pool,
mkl_op_registry::GetMklOpName(csinfo_.max_pool),
CopyAttrsPooling, NonDepthBatchWisePoolRewrite});
rinfo_.push_back({csinfo_.max_pool_grad,
mkl_op_registry::GetMklOpName(csinfo_.max_pool_grad),
- CopyAttrsPooling, AlwaysRewrite});
+ CopyAttrsPooling, MaxpoolGradRewrite});
rinfo_.push_back({csinfo_.maximum,
mkl_op_registry::GetMklOpName(csinfo_.maximum),
@@ -2887,6 +2886,41 @@ class MklLayoutRewritePass : public GraphOptimizationPass {
return false;
}
+ static bool LrnGradRewrite(const Node* n) {
+ CHECK_NOTNULL(n);
+ bool do_rewrite = false;
+
+ for (const Edge* e : n->in_edges()) {
+ // Rewrite only if there is corresponding LRN, i.e workspace is available
+ if (e->dst()->type_string() == csinfo_.lrn_grad && e->dst_input() == 2 &&
+ e->src()->type_string() ==
+ mkl_op_registry::GetMklOpName(csinfo_.lrn) &&
+ e->src_output() == 0) {
+ do_rewrite = true;
+ break;
+ }
+ }
+ return do_rewrite;
+ }
+
+ static bool MaxpoolGradRewrite(const Node* n) {
+ CHECK_NOTNULL(n);
+ bool do_rewrite = false;
+ for (const Edge* e : n->in_edges()) {
+ // Rewrite only if there is corresponding Maxpool, i.e workspace is
+ // available
+ if (e->dst()->type_string() == csinfo_.max_pool_grad &&
+ e->dst_input() == 1 &&
+ e->src()->type_string() ==
+ mkl_op_registry::GetMklOpName(csinfo_.max_pool) &&
+ e->src_output() == 0) {
+ do_rewrite = true;
+ break;
+ }
+ }
+ return do_rewrite;
+ }
+
static bool AddNRewrite(const Node* n) {
CHECK_NOTNULL(n);
@@ -3421,44 +3455,9 @@ Status MklLayoutRewritePass::SetUpInputs(
// TODO(nhasabni) We should move this to mkl_util.h.
void MklLayoutRewritePass::GetDummyWorkspaceTensorNode(
std::unique_ptr<Graph>* g, Node** out, Node* orig_node) {
- // We use a tensor of shape {1} and value 0 to represent
- // dummy float tensor. We need this as a dummy workspace tensor.
- // Workspace tensor has type uint8.
- const DataType dt = DataTypeToEnum<uint8>::v();
- TensorProto proto;
- proto.set_dtype(dt);
- float zero[1] = {0};
- proto.set_tensor_content(string(reinterpret_cast<char*>(&zero), 4));
- TensorShape dummy_shape({1});
- dummy_shape.AsProto(proto.mutable_tensor_shape());
- TF_CHECK_OK(NodeBuilder((*g)->NewName("DMT"), "Const")
- .Attr("value", proto)
- .Attr("dtype", dt)
- .Device(orig_node->def().device()) // We place this node on
- // same the device as the
- // device of the original
- // node.
- .Finalize(&**g, out));
-
- // If number of inputs to the original node is > 0, then we add
- // control dependency between 1st input (index 0) of the original node and
- // the dummy Mkl node. This is needed because control-flow ops such as Enter,
- // Merge, etc, require frame_name of the dummy Mkl node to be same as the
- // rewritten node. Adding control edge between 1st input of the original node
- // and the dummy Mkl node ensures that the dummy node is in the same frame
- // as the original node. Choosing 1st input is not necessary - any input of
- // the original node is fine because all the inputs of a node are always in
- // the same frame.
- if (orig_node->num_inputs() > 0) {
- Node* orig_input0 = nullptr;
- TF_CHECK_OK(
- orig_node->input_node(0, const_cast<const Node**>(&orig_input0)));
- // Allow duplicate while adding control edge as it would fail (return
- // NULL) if we try to add duplicate edge.
- CHECK_NOTNULL((*g)->AddControlEdge(orig_input0, *out, true));
- }
-
- (*out)->set_assigned_device_name(orig_node->assigned_device_name());
+ // We use uint8 tensor of shape 8 with content {0,0,0,0,0,0,0,0} to represent
+ // workspace tensor.
+ GetDummyMklTensorNode(g, out, orig_node);
}
void MklLayoutRewritePass::AddWorkSpaceEdgeIfNeeded(
diff --git a/tensorflow/core/graph/mkl_layout_pass_test.cc b/tensorflow/core/graph/mkl_layout_pass_test.cc
index fc474c0dc8..a41f5861af 100644
--- a/tensorflow/core/graph/mkl_layout_pass_test.cc
+++ b/tensorflow/core/graph/mkl_layout_pass_test.cc
@@ -19,7 +19,6 @@ limitations under the License.
#include "tensorflow/core/graph/mkl_graph_util.h"
#include <algorithm>
-#include <string>
#include <vector>
#include "tensorflow/core/framework/op.h"
@@ -3015,12 +3014,8 @@ TEST_F(MklLayoutPassTest, LRN_Negative2) {
"node { name: 'E' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }"
" input: ['A', 'D'] }");
EXPECT_EQ(DoMklLayoutOptimizationPass(),
- "A(Input);B(Input);C(Input);D(_MklLRNGrad);DMT/_0(Const);"
- "DMT/_1(Const);DMT/_2(Const);DMT/_3(Const);DMT/_4(Const);E(Zeta)|"
- "A->D;A->E;A:control->DMT/_0:control;A:control->DMT/_1:control;"
- "A:control->DMT/_2:control;A:control->DMT/_3:control;"
- "A:control->DMT/_4:control;B->D:1;C->D:2;D->E:1;DMT/_0->D:3;"
- "DMT/_1->D:7;DMT/_2->D:4;DMT/_3->D:5;DMT/_4->D:6");
+ "A(Input);B(Input);C(Input);D(LRNGrad);"
+ "E(Zeta)|A->D;A->E;B->D:1;C->D:2;D->E:1");
}
/* Test LRN->LRNGrad negative case, where single LRN feeds
@@ -3058,15 +3053,11 @@ TEST_F(MklLayoutPassTest, LRN_Negative3) {
" input: ['E', 'F'] }");
EXPECT_EQ(DoMklLayoutOptimizationPass(),
"A(Input);B(_MklLRN);C(Input);D(Input);DMT/_0(Const);DMT/_1(Const);"
- "DMT/_2(Const);DMT/_3(Const);DMT/_4(Const);DMT/_5(Const);"
- "DMT/_6(Const);E(_MklLRNGrad);F(_MklLRNGrad);G(Zeta)|A->B;"
- "A:control->DMT/_0:control;B->E:2;"
- "B->F:1;B:1->E:3;B:2->E:6;B:2->F:5;B:3->E:7;C->E;C->F;"
- "C:control->DMT/_1:control;C:control->DMT/_2:control;"
- "C:control->DMT/_3:control;C:control->DMT/_4:control;"
- "C:control->DMT/_5:control;C:control->DMT/_6:control;"
- "D->E:1;D->F:2;DMT/_0->B:1;DMT/_1->E:4;DMT/_2->E:5;DMT/_3->F:3;"
- "DMT/_4->F:7;DMT/_5->F:4;DMT/_6->F:6;E->G;F->G:1");
+ "DMT/_2(Const);E(_MklLRNGrad);F(LRNGrad);G(Zeta)|A->B;"
+ "A:control->DMT/_0:control;B->E:2;B->F:1;B:1->E:3;B:2->E:6;"
+ "B:3->E:7;C->E;C->F;C:control->DMT/_1:control;"
+ "C:control->DMT/_2:control;D->E:1;D->F:2;DMT/_0->B:1;"
+ "DMT/_1->E:4;DMT/_2->E:5;E->G;F->G:1");
}
/* Test MaxPool->MaxPoolGrad replacement by workspace+rewrite nodes. */
@@ -3137,12 +3128,8 @@ TEST_F(MklLayoutPassTest, NodeWorkspace_MaxPool_Negative2) {
"node { name: 'E' op: 'Zeta' attr { key: 'T' value { type: DT_FLOAT } }"
" input: ['A', 'D'] }");
EXPECT_EQ(DoMklLayoutOptimizationPass(),
- "A(Input);B(Input);C(Input);D(_MklMaxPoolGrad);DMT/_0(Const);"
- "DMT/_1(Const);DMT/_2(Const);DMT/_3(Const);DMT/_4(Const);E(Zeta)|"
- "A->D;A->E;A:control->DMT/_0:control;A:control->DMT/_1:control;"
- "A:control->DMT/_2:control;A:control->DMT/_3:control;"
- "A:control->DMT/_4:control;B->D:1;C->D:2;D->E:1;DMT/_0->D:3;"
- "DMT/_1->D:7;DMT/_2->D:4;DMT/_3->D:5;DMT/_4->D:6");
+ "A(Input);B(Input);C(Input);D(MaxPoolGrad);"
+ "E(Zeta)|A->D;A->E;B->D:1;C->D:2;D->E:1");
}
// Test MaxPool handling for batch-wise pooling (NCHW)
diff --git a/tensorflow/core/graph/mkl_tfconversion_pass.cc b/tensorflow/core/graph/mkl_tfconversion_pass.cc
index e9ced4d2b6..aa39af637f 100644
--- a/tensorflow/core/graph/mkl_tfconversion_pass.cc
+++ b/tensorflow/core/graph/mkl_tfconversion_pass.cc
@@ -18,7 +18,6 @@ limitations under the License.
#include <memory>
#include <queue>
#include <set>
-#include <string>
#include <utility>
#include <vector>
diff --git a/tensorflow/core/graph/mkl_tfconversion_pass_test.cc b/tensorflow/core/graph/mkl_tfconversion_pass_test.cc
index bbdbe78bbd..ebcb6de551 100644
--- a/tensorflow/core/graph/mkl_tfconversion_pass_test.cc
+++ b/tensorflow/core/graph/mkl_tfconversion_pass_test.cc
@@ -19,7 +19,6 @@ limitations under the License.
#include "tensorflow/core/graph/mkl_graph_util.h"
#include <algorithm>
-#include <string>
#include <vector>
#include "tensorflow/core/framework/op.h"
diff --git a/tensorflow/core/grappler/BUILD b/tensorflow/core/grappler/BUILD
index 9dcc6765f5..7c6fe56e1f 100644
--- a/tensorflow/core/grappler/BUILD
+++ b/tensorflow/core/grappler/BUILD
@@ -33,6 +33,7 @@ tf_cc_test(
name = "utils_test",
srcs = ["utils_test.cc"],
deps = [
+ ":grappler_item",
":utils",
"//tensorflow/cc:cc_ops",
"//tensorflow/core:all_kernels",
@@ -151,3 +152,32 @@ tf_cc_test(
"//tensorflow/core/grappler/inputs:trivial_test_graph_input_yielder",
],
)
+
+cc_library(
+ name = "mutable_graph_view",
+ srcs = [
+ "mutable_graph_view.cc",
+ ],
+ hdrs = ["mutable_graph_view.h"],
+ visibility = ["//visibility:public"],
+ deps = [
+ ":graph_view",
+ ":grappler_item",
+ ":utils",
+ "//tensorflow/core:lib",
+ "//tensorflow/core:protos_all_cc",
+ ],
+)
+
+tf_cc_test(
+ name = "mutable_graph_view_test",
+ srcs = ["mutable_graph_view_test.cc"],
+ deps = [
+ ":grappler_item",
+ ":mutable_graph_view",
+ "//tensorflow/cc:cc_ops",
+ "//tensorflow/core:test",
+ "//tensorflow/core:test_main",
+ "//tensorflow/core/grappler/inputs:trivial_test_graph_input_yielder",
+ ],
+)
diff --git a/tensorflow/core/grappler/clusters/cluster.cc b/tensorflow/core/grappler/clusters/cluster.cc
index 6d84283e68..6ca379323e 100644
--- a/tensorflow/core/grappler/clusters/cluster.cc
+++ b/tensorflow/core/grappler/clusters/cluster.cc
@@ -42,6 +42,11 @@ void Cluster::SetNumWarmupSteps(int num_steps) {
num_steps);
}
+// Set executor type to instantiate
+void Cluster::SetExecutorType(const string* executor_type) {
+ options_.config.mutable_experimental()->set_executor_type(*executor_type);
+}
+
int Cluster::NumWarmupSteps() const {
return options_.config.graph_options().build_cost_model_after();
}
diff --git a/tensorflow/core/grappler/clusters/cluster.h b/tensorflow/core/grappler/clusters/cluster.h
index e94fb900c0..519d5ed875 100644
--- a/tensorflow/core/grappler/clusters/cluster.h
+++ b/tensorflow/core/grappler/clusters/cluster.h
@@ -72,6 +72,9 @@ class Cluster {
// before Provision().
void SetNumWarmupSteps(int num_steps);
+ // Set executor type to instantiate
+ void SetExecutorType(const string* executor_type);
+
// Returns the number of warmup steps.
int NumWarmupSteps() const;
diff --git a/tensorflow/core/grappler/graph_view.cc b/tensorflow/core/grappler/graph_view.cc
index 3e448216f9..7998f0a902 100644
--- a/tensorflow/core/grappler/graph_view.cc
+++ b/tensorflow/core/grappler/graph_view.cc
@@ -22,28 +22,33 @@ namespace grappler {
GraphView::GraphView(GraphDef* graph) : graph_(graph) {
for (int i = 0; i < graph_->node_size(); i++) {
auto node = graph_->mutable_node(i);
- auto rslt = nodes_.insert(std::make_pair(node->name(), node));
+ auto result = nodes_.emplace(node->name(), node);
// Check that the graph doesn't contain multiple nodes with the same name.
- CHECK(rslt.second) << "Non unique node name detected: " << node->name();
+ CHECK(result.second) << "Non unique node name detected: " << node->name();
}
+
for (NodeDef& node : *graph_->mutable_node()) {
- for (int i = 0; i < node.input_size(); ++i) {
- OutputPort fanin;
- string fanin_name = ParseNodeName(node.input(i), &fanin.port_id);
- fanin.node = nodes_[fanin_name];
+ AddFanouts(&node);
+ }
+}
- InputPort input;
- input.node = &node;
- if (fanin.port_id < 0) {
- input.port_id = -1;
- } else {
- input.port_id = i;
- num_regular_outputs_[fanin.node] =
- std::max(num_regular_outputs_[fanin.node], fanin.port_id);
- }
+void GraphView::AddFanouts(NodeDef* node) {
+ for (int i = 0; i < node->input_size(); ++i) {
+ OutputPort fanin;
+ string fanin_name = ParseNodeName(node->input(i), &fanin.port_id);
+ fanin.node = nodes_[fanin_name];
- fanouts_[fanin].insert(input);
+ InputPort input;
+ input.node = node;
+ if (fanin.port_id < 0) {
+ input.port_id = -1;
+ } else {
+ input.port_id = i;
+ num_regular_outputs_[fanin.node] =
+ std::max(num_regular_outputs_[fanin.node], fanin.port_id);
}
+
+ fanouts_[fanin].insert(input);
}
}
diff --git a/tensorflow/core/grappler/graph_view.h b/tensorflow/core/grappler/graph_view.h
index 584cb9048b..050789d2e2 100644
--- a/tensorflow/core/grappler/graph_view.h
+++ b/tensorflow/core/grappler/graph_view.h
@@ -29,8 +29,11 @@ namespace grappler {
class GraphView {
public:
struct Port {
- Port() : node(nullptr), port_id(-1) {}
+ Port() = default;
Port(NodeDef* n, int port) : node(n), port_id(port) {}
+
+ // TODO(prazek): ports should keep the constness of GraphView. The only way
+ // to modify graph through the view should be using MutableGraphView.
NodeDef* node = nullptr;
int port_id = -1;
@@ -111,13 +114,22 @@ class GraphView {
std::unordered_set<Edge, HashEdge> GetFaninEdges(
const NodeDef& node, bool include_controlling_edges) const;
+ protected:
+ // Add fanout to every `node` input.
+ void AddFanouts(NodeDef* node);
+ std::unordered_map<string, NodeDef*>* MutableNodes() { return &nodes_; }
+ GraphDef* MutableGraph() { return graph_; }
+
+ using FanoutsMapType =
+ std::unordered_map<OutputPort, std::unordered_set<InputPort, HashPort>,
+ HashPort>;
+ FanoutsMapType* MutableFanouts() { return &fanouts_; }
+
private:
GraphDef* graph_;
std::unordered_map<string, NodeDef*> nodes_;
std::unordered_set<InputPort, HashPort> empty_set_;
- std::unordered_map<OutputPort, std::unordered_set<InputPort, HashPort>,
- HashPort>
- fanouts_;
+ FanoutsMapType fanouts_;
std::unordered_map<const NodeDef*, int> num_regular_outputs_;
};
diff --git a/tensorflow/core/grappler/mutable_graph_view.cc b/tensorflow/core/grappler/mutable_graph_view.cc
new file mode 100644
index 0000000000..6abafe11a2
--- /dev/null
+++ b/tensorflow/core/grappler/mutable_graph_view.cc
@@ -0,0 +1,72 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/core/grappler/mutable_graph_view.h"
+#include "tensorflow/core/grappler/utils.h"
+
+namespace tensorflow {
+namespace grappler {
+
+NodeDef* MutableGraphView::AddNode(NodeDef&& node) {
+ auto* node_in_graph = GetGraph()->add_node();
+ *node_in_graph = std::move(node);
+
+ auto result = MutableNodes()->emplace(node_in_graph->name(), node_in_graph);
+ // Check that the graph doesn't contain multiple nodes with the same name.
+ CHECK(result.second) << "Non unique node name detected: "
+ << node_in_graph->name();
+ AddFanouts(node_in_graph);
+ return node_in_graph;
+}
+
+void MutableGraphView::ReplaceInput(const NodeDef& old_input,
+ const NodeDef& new_input,
+ const int output_port_id) {
+ GraphView::OutputPort output_port =
+ GetOutputPort(old_input.name(), output_port_id);
+ auto fanout = GetFanout(output_port);
+ for (auto& input_port : fanout) {
+ input_port.node->set_input(input_port.port_id, new_input.name());
+ AddFanouts(input_port.node);
+ }
+}
+
+void MutableGraphView::DeleteNodes(const std::set<string>& nodes_to_delete) {
+ for (const string& node_name_to_delete : nodes_to_delete)
+ RemoveFanouts(MutableNodes()->at(node_name_to_delete));
+ for (const string& node_name_to_delete : nodes_to_delete)
+ MutableNodes()->erase(node_name_to_delete);
+ EraseNodesFromGraph(nodes_to_delete, GetGraph());
+}
+
+void MutableGraphView::RemoveFanouts(NodeDef* node) {
+ for (int i = 0; i < node->input_size(); ++i) {
+ OutputPort fanin;
+ string fanin_name = ParseNodeName(node->input(i), &fanin.port_id);
+ fanin.node = (*MutableNodes())[fanin_name];
+
+ InputPort input;
+ input.node = node;
+ if (fanin.port_id < 0)
+ input.port_id = -1;
+ else
+ input.port_id = i;
+
+ (*MutableFanouts())[fanin].erase(input);
+ }
+}
+
+} // end namespace grappler
+} // end namespace tensorflow
diff --git a/tensorflow/core/grappler/mutable_graph_view.h b/tensorflow/core/grappler/mutable_graph_view.h
new file mode 100644
index 0000000000..105eb972e8
--- /dev/null
+++ b/tensorflow/core/grappler/mutable_graph_view.h
@@ -0,0 +1,56 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#ifndef TENSORFLOW_CORE_GRAPPLER_MUTABLE_GRAPH_VIEW_H_
+#define TENSORFLOW_CORE_GRAPPLER_MUTABLE_GRAPH_VIEW_H_
+
+#include "tensorflow/core/grappler/graph_view.h"
+
+namespace tensorflow {
+namespace grappler {
+
+// A utility class to simplify the traversal of a GraphDef that, unlike
+// GraphView, supports updating the graph. Note that you should not modify the
+// graph separately, because the view will get out of sync.
+class MutableGraphView : public GraphView {
+ public:
+ using GraphView::GraphView;
+
+ GraphDef* GetGraph() { return MutableGraph(); }
+ // Adds a new node to graph and updates the view.
+ NodeDef* AddNode(NodeDef&& node);
+
+ // Replaces the input for the output nodes of 'old_input' with a port
+ // `output_port_id` with 'new_input'.
+ //
+ // E.g: We have 2 nodes that use 'bar' node outputs as inputs:
+ // foo(bar:0, bar:1), foo2(other:0, bar:0)
+ // Calling ReplaceInput(bar, new, 0) changes every occurrence of bar:0 for
+ // new:0. Result:
+ // foo(new:0, bar:1), foo2(other:0, new:0)
+ void ReplaceInput(const NodeDef& old_input, const NodeDef& new_input,
+ int output_port_id = 0);
+
+ // Deletes nodes from the graph.
+ void DeleteNodes(const std::set<string>& nodes_to_delete);
+
+ private:
+ void RemoveFanouts(NodeDef* node);
+};
+
+} // end namespace grappler
+} // end namespace tensorflow
+
+#endif // TENSORFLOW_CORE_GRAPPLER_MUTABLE_GRAPH_VIEW_H_
diff --git a/tensorflow/core/grappler/mutable_graph_view_test.cc b/tensorflow/core/grappler/mutable_graph_view_test.cc
new file mode 100644
index 0000000000..f09dfb8271
--- /dev/null
+++ b/tensorflow/core/grappler/mutable_graph_view_test.cc
@@ -0,0 +1,94 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/core/grappler/mutable_graph_view.h"
+#include "tensorflow/cc/ops/standard_ops.h"
+#include "tensorflow/core/grappler/grappler_item.h"
+#include "tensorflow/core/grappler/inputs/trivial_test_graph_input_yielder.h"
+#include "tensorflow/core/platform/test.h"
+
+namespace tensorflow {
+namespace grappler {
+namespace {
+
+TEST(MutableGraphViewTest, AddAndReplaceInput) {
+ // This outputs simple graph like:
+ // x
+ // / \
+ // Square Square_1
+ // | \ / |
+ // | \/ |
+ // | /\ |
+ // | / \ |
+ // AddN AddN_1
+ // \ /
+ // y
+ TrivialTestGraphInputYielder fake_input(2, 2, 2, false, {"/CPU:0", "/GPU:0"});
+ GrapplerItem item;
+ CHECK(fake_input.NextItem(&item));
+
+ GraphDef new_graph = item.graph;
+ MutableGraphView graph(&new_graph);
+
+ GraphView::InputPort input = graph.GetInputPort("AddN", 0);
+ EXPECT_EQ("AddN", input.node->name());
+ EXPECT_EQ(0, input.port_id);
+ GraphView::OutputPort fanin = graph.GetRegularFanin(input);
+ EXPECT_EQ("Square", fanin.node->name());
+ EXPECT_EQ(0, fanin.port_id);
+
+ auto find_child_with_name = [&graph](string output_port_name,
+ string input_name) {
+ GraphView::OutputPort output_port =
+ graph.GetOutputPort(output_port_name, 0);
+ auto fanout = graph.GetFanout(output_port);
+ for (auto& input_port : fanout) {
+ if (input_port.node->name() == input_name) return true;
+ }
+ return false;
+ };
+
+ EXPECT_FALSE(find_child_with_name("Square", "new_node"));
+
+ NodeDef new_node = *input.node;
+ new_node.set_name("new_node");
+
+ EXPECT_EQ(graph.GetNode("new_node"), nullptr);
+ NodeDef* node_in_graph = graph.AddNode(std::move(new_node));
+ EXPECT_NE(graph.GetNode("new_node"), nullptr);
+
+ graph.ReplaceInput(*input.node, *node_in_graph);
+ EXPECT_TRUE(find_child_with_name("Square", "new_node"));
+ EXPECT_TRUE(find_child_with_name("new_node", "y"));
+}
+
+TEST(MutableGraphViewTest, DeleteNodes) {
+ // Outputs simple graph as described in first test.
+ TrivialTestGraphInputYielder fake_input(2, 2, 2, false, {"/CPU:0", "/GPU:0"});
+ GrapplerItem item;
+ CHECK(fake_input.NextItem(&item));
+
+ GraphDef new_graph = item.graph;
+ MutableGraphView graph(&new_graph);
+
+ EXPECT_NE(graph.GetNode("AddN"), nullptr);
+ graph.DeleteNodes({"AddN"});
+
+ EXPECT_EQ(graph.GetNode("AddN"), nullptr);
+}
+
+} // namespace
+} // namespace grappler
+} // namespace tensorflow
diff --git a/tensorflow/core/grappler/op_types.cc b/tensorflow/core/grappler/op_types.cc
index bdeb5c66fc..653b088b1d 100644
--- a/tensorflow/core/grappler/op_types.cc
+++ b/tensorflow/core/grappler/op_types.cc
@@ -161,6 +161,8 @@ bool IsExit(const NodeDef& node) {
return op == "Exit" || op == "RefExit";
}
+bool IsExp(const NodeDef& node) { return node.op() == "Exp"; }
+
bool IsFill(const NodeDef& node) { return node.op() == "Fill"; }
bool IsFloorDiv(const NodeDef& node) { return node.op() == "FloorDiv"; }
diff --git a/tensorflow/core/grappler/op_types.h b/tensorflow/core/grappler/op_types.h
index 2de7d8cc9a..94439265c9 100644
--- a/tensorflow/core/grappler/op_types.h
+++ b/tensorflow/core/grappler/op_types.h
@@ -60,6 +60,7 @@ bool IsEluGrad(const NodeDef& node);
bool IsEnter(const NodeDef& node);
bool IsEqual(const NodeDef& node);
bool IsExit(const NodeDef& node);
+bool IsExp(const NodeDef& node);
bool IsFill(const NodeDef& node);
bool IsFloorDiv(const NodeDef& node);
bool IsFloorMod(const NodeDef& node);
diff --git a/tensorflow/core/grappler/optimizers/BUILD b/tensorflow/core/grappler/optimizers/BUILD
index b1d6d48e31..caaa5ac8db 100644
--- a/tensorflow/core/grappler/optimizers/BUILD
+++ b/tensorflow/core/grappler/optimizers/BUILD
@@ -95,6 +95,7 @@ cc_library(
],
visibility = ["//visibility:public"],
deps = [
+ ":evaluation_utils",
":graph_optimizer",
":symbolic_shapes",
"//tensorflow/core:framework",
@@ -603,7 +604,9 @@ cc_library(
visibility = ["//visibility:public"],
deps = [
":constant_folding",
+ ":evaluation_utils",
":graph_optimizer",
+ "//tensorflow/core:core_cpu_lib",
"//tensorflow/core:framework",
"//tensorflow/core:lib",
"//tensorflow/core:lib_internal",
@@ -624,6 +627,7 @@ tf_cuda_cc_test(
":loop_optimizer",
"//tensorflow/cc:cc_ops",
"//tensorflow/core:protos_all_cc",
+ "//tensorflow/core:tensor_testutil",
"//tensorflow/core:test",
"//tensorflow/core:test_main",
"//tensorflow/core/grappler:grappler_item",
@@ -810,3 +814,39 @@ tf_cc_test(
"//tensorflow/core/grappler/inputs:trivial_test_graph_input_yielder",
],
)
+
+cc_library(
+ name = "evaluation_utils",
+ srcs = ["evaluation_utils.cc"],
+ hdrs = [
+ "evaluation_utils.h",
+ ],
+ visibility = ["//visibility:public"],
+ deps = [
+ "//tensorflow/core:framework",
+ "//tensorflow/core:lib",
+ "//tensorflow/core:lib_internal",
+ "//tensorflow/core:protos_all_cc",
+ "//tensorflow/core/grappler:grappler_item",
+ "//tensorflow/core/grappler:op_types",
+ "//tensorflow/core/grappler:utils",
+ "//tensorflow/core/grappler/clusters:cluster",
+ "//tensorflow/core/grappler/costs:graph_properties",
+ ],
+)
+
+tf_cc_test(
+ name = "evaluation_utils_test",
+ srcs = ["evaluation_utils_test.cc"],
+ deps = [
+ ":evaluation_utils",
+ "//tensorflow/core:core_cpu",
+ "//tensorflow/core:framework",
+ "//tensorflow/core:lib",
+ "//tensorflow/core:protos_all_cc",
+ "//tensorflow/core:test",
+ "//tensorflow/core:test_main",
+ "//tensorflow/core:testlib",
+ "//third_party/eigen3",
+ ],
+)
diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc
index 97862d1ed0..889445bbd6 100644
--- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc
+++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.cc
@@ -178,6 +178,42 @@ NodeDef* GetTailOfIdempotentChain(
is_idempotent_non_branching);
}
+// GetElementUnexhaustive tries to get the value of an element in a tensor and
+// turn it into complex128 type. It only check for a limited number of data
+// types, so it's unexhaustive.
+bool GetElementUnexhaustive(const Tensor& t, int i, const std::set<int>& dtypes,
+ complex128* element) {
+ if (dtypes.find(t.dtype()) == dtypes.end()) return false;
+ switch (t.dtype()) {
+ case DT_BFLOAT16:
+ *element = complex128(t.flat<bfloat16>()(i));
+ return true;
+ case DT_HALF:
+ *element = complex128(static_cast<double>(t.flat<Eigen::half>()(i)), 0);
+ return true;
+ case DT_INT32:
+ *element = complex128(t.flat<int32>()(i));
+ return true;
+ case DT_INT64:
+ *element = complex128(t.flat<int64>()(i));
+ return true;
+ case DT_FLOAT:
+ *element = complex128(t.flat<float>()(i));
+ return true;
+ case DT_DOUBLE:
+ *element = complex128(t.flat<double>()(i));
+ return true;
+ case DT_COMPLEX64:
+ *element = complex128(t.flat<complex64>()(i));
+ return true;
+ case DT_COMPLEX128:
+ *element = t.flat<complex128>()(i);
+ return true;
+ default:
+ return false;
+ }
+}
+
// Graph optimizer context extension specific to ArithmeticOptimizer.
struct ArithmeticOptimizerContext {
explicit ArithmeticOptimizerContext(SetVector<NodeDef*>* nodes_to_simplify)
@@ -2361,7 +2397,13 @@ class ConvertPowStage : public ArithmeticOptimizerStage {
complex128 prev, curr;
for (int i = 0; i < pow.NumElements(); ++i) {
- TF_RETURN_IF_ERROR(GetElement(pow, i, &curr));
+ if (!GetElementUnexhaustive(pow, i,
+ {DT_INT32, DT_INT64, DT_FLOAT, DT_DOUBLE,
+ DT_COMPLEX64, DT_COMPLEX128},
+ &curr)) {
+ // input data type is not supported by Pow. Skip.
+ return Status::OK();
+ }
if (i != 0 && curr != prev) {
// pow has different values on different elements. Skip.
return Status::OK();
@@ -2432,31 +2474,6 @@ class ConvertPowStage : public ArithmeticOptimizerStage {
}
private:
- Status GetElement(const Tensor& t, int i, complex128* element) {
- switch (t.dtype()) {
- case DT_INT32:
- *element = complex128(t.flat<int32>()(i));
- return Status::OK();
- case DT_INT64:
- *element = complex128(t.flat<int64>()(i));
- return Status::OK();
- case DT_FLOAT:
- *element = complex128(t.flat<float>()(i));
- return Status::OK();
- case DT_DOUBLE:
- *element = complex128(t.flat<double>()(i));
- return Status::OK();
- case DT_COMPLEX64:
- *element = complex128(t.flat<complex64>()(i));
- return Status::OK();
- case DT_COMPLEX128:
- *element = t.flat<complex128>()(i);
- return Status::OK();
- default:
- return errors::InvalidArgument("Invalid data type: ", t.dtype());
- }
- }
-
Status SetElementToOne(int i, Tensor* t) {
switch (t->dtype()) {
case DT_INT32:
@@ -2544,7 +2561,10 @@ class ConvertLog1pStage : public ArithmeticOptimizerStage {
}
complex128 element;
for (int k = 0; k < constant.NumElements(); ++k) {
- if (!GetElement(constant, k, &element)) {
+ if (!GetElementUnexhaustive(constant, k,
+ {DT_BFLOAT16, DT_HALF, DT_FLOAT, DT_DOUBLE,
+ DT_COMPLEX64, DT_COMPLEX128},
+ &element)) {
// input data type is not supported by log1p. Skip.
return Status::OK();
}
@@ -2569,30 +2589,94 @@ class ConvertLog1pStage : public ArithmeticOptimizerStage {
}
return Status::OK();
}
+};
- bool GetElement(const Tensor& t, int i, complex128* element) {
- switch (t.dtype()) {
- case DT_BFLOAT16:
- *element = complex128(t.flat<bfloat16>()(i));
- return true;
- case DT_HALF:
- *element = complex128(static_cast<double>(t.flat<Eigen::half>()(i)), 0);
- return true;
- case DT_FLOAT:
- *element = complex128(t.flat<float>()(i));
- return true;
- case DT_DOUBLE:
- *element = complex128(t.flat<double>()(i));
- return true;
- case DT_COMPLEX64:
- *element = complex128(t.flat<complex64>()(i));
- return true;
- case DT_COMPLEX128:
- *element = t.flat<complex128>()(i);
- return true;
- default:
- return false;
+class ConvertExpm1Stage : public ArithmeticOptimizerStage {
+ public:
+ explicit ConvertExpm1Stage(const GraphOptimizerContext& ctx,
+ const ArithmeticOptimizerContext& ctx_ext)
+ : ArithmeticOptimizerStage("ConvertExpm1", ctx, ctx_ext) {}
+ ~ConvertExpm1Stage() override = default;
+
+ bool IsSupported(const NodeDef* node) const override {
+ if (!IsSub(*node))
+ return false;
+
+ NodeDef* input;
+ if (!GetInputNode(node->input(0), &input).ok())
+ return false;
+
+ return IsExp(*input);
+ }
+
+ Status TrySimplify(NodeDef* node, string* simplified_node_name) override {
+ if (ctx().graph_properties->GetInputProperties(node->name()).size() < 2) {
+ return Status::OK();
+ }
+
+ NodeDef* exp;
+ TF_RETURN_IF_ERROR(GetInputNode(node->input(0), &exp));
+ if (!IsExp(*exp)) {
+ return Status::OK();
+ }
+
+ if (ctx().graph_properties->GetInputProperties(exp->name()).empty()) {
+ return Status::OK();
+ }
+
+ const auto& t =
+ ctx().graph_properties->GetInputProperties(exp->name())[0];
+ const auto& c =
+ ctx().graph_properties->GetInputProperties(node->name())[1];
+ for (int k = 0; k < c.shape().dim_size(); ++k) {
+ // Skip if c shape is not fully determined.
+ if (c.shape().dim(k).size() < 0) {
+ return Status::OK();
+ }
+ }
+ TensorShapeProto broadcast_shape;
+ if (!ShapeAfterBroadcast(t.shape(), c.shape(), &broadcast_shape)) {
+ return Status::OK();
}
+ if (!ShapesSymbolicallyEqual(t.shape(), broadcast_shape)) {
+ // skip if the non-constant tensor doesn't have the same shape after
+ // broadcast.
+ return Status::OK();
+ }
+ if (TensorShape::IsValid(c.shape()) && c.has_value()) {
+ Tensor constant(c.dtype(), c.shape());
+ if (!constant.FromProto(c.value())) {
+ return errors::InvalidArgument("Cannot parse tensor from proto: ",
+ c.value().DebugString());
+ }
+ complex128 element;
+ for (int k = 0; k < constant.NumElements(); ++k) {
+ if (!GetElementUnexhaustive(constant, k,
+ {DT_BFLOAT16, DT_HALF, DT_FLOAT, DT_DOUBLE,
+ DT_COMPLEX64, DT_COMPLEX128},
+ &element)) {
+ // input data type is not supported by expm1. Skip.
+ return Status::OK();
+ }
+ if (element != complex128(1)) {
+ // current element is not 1. Skip.
+ return Status::OK();
+ }
+ }
+ NodeDef *exp_input, *ones;
+ TF_RETURN_IF_ERROR(GetInputNode(exp->input(0), &exp_input));
+ TF_RETURN_IF_ERROR(GetInputNode(node->input(1), &ones));
+ node->set_op("Expm1");
+ node->set_input(0, exp->input(0));
+ node->set_input(1, AsControlDependency(ones->name()));
+ ForwardControlDependencies(node, {exp});
+
+ AddToOptimizationQueue(node);
+ AddToOptimizationQueue(exp);
+ AddToOptimizationQueue(exp_input);
+ AddToOptimizationQueue(ones);
+ }
+ return Status::OK();
}
};
@@ -3007,14 +3091,7 @@ void ArithmeticOptimizer::DedupComputations() {
// Delete duplicates
if (fetch_nodes_known_ && !duplicates.empty()) {
- int last = optimized_graph_->node_size() - 1;
- for (auto it = duplicates.rbegin(); it != duplicates.rend(); ++it) {
- int index = *it;
- optimized_graph_->mutable_node()->SwapElements(index, last);
- last--;
- }
- optimized_graph_->mutable_node()->DeleteSubrange(last + 1,
- duplicates.size());
+ EraseNodesFromGraph(duplicates, optimized_graph_);
// Rebuild the NodeMap which was invalidated by the node swapping above.
node_map_.reset(new NodeMap(optimized_graph_));
}
@@ -3094,6 +3171,8 @@ Status ArithmeticOptimizer::SimplifyArithmeticOps(bool can_use_shapes) {
pipeline.AddStage<ConvertLog1pStage>(ctx, ctx_ext);
if (options_.optimize_max_or_min_of_monotonic)
pipeline.AddStage<OptimizeMaxOrMinOfMonotonicStage>(ctx, ctx_ext);
+ if (options_.convert_expm1)
+ pipeline.AddStage<ConvertExpm1Stage>(ctx, ctx_ext);
if (options_.unary_ops_composition)
pipeline.AddStage<UnaryOpsComposition>(ctx, ctx_ext);
diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h
index 00c02d19bd..551c3652bf 100644
--- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h
+++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer.h
@@ -77,6 +77,7 @@ class ArithmeticOptimizer : public GraphOptimizer {
bool simplify_aggregation = true;
bool convert_pow = true;
bool convert_log1p = true;
+ bool convert_expm1 = true;
bool unary_ops_composition = true;
// Choose which arithmetic optimizer stages will be enabled for a given
diff --git a/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc b/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc
index c387b00303..85cb19d419 100644
--- a/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc
+++ b/tensorflow/core/grappler/optimizers/arithmetic_optimizer_test.cc
@@ -279,6 +279,11 @@ class ArithmeticOptimizerTest : public GrapplerTest {
optimizer->options_.optimize_max_or_min_of_monotonic = true;
}
+ void EnableOnlyExpm1(ArithmeticOptimizer* optimizer) {
+ DisableAllStages(optimizer);
+ optimizer->options_.convert_expm1 = true;
+ }
+
void EnableOnlyUnaryOpsComposition(ArithmeticOptimizer* optimizer) {
DisableAllStages(optimizer);
optimizer->options_.unary_ops_composition = true;
@@ -2542,6 +2547,47 @@ TEST_F(ArithmeticOptimizerTest, Log1p) {
CompareGraphs(want, got);
}
+TEST_F(ArithmeticOptimizerTest, Expm1) {
+ tensorflow::Scope s = tensorflow::Scope::NewRootScope();
+
+ auto x1 = ops::Const(s.WithOpName("x1"), {2.0f, 2.0f}, {1, 2});
+ auto x2 = ops::Const(s.WithOpName("x2"), {1.0f, 1.0f}, {1, 2});
+ auto x3 = ops::Const(s.WithOpName("x3"), {3.0f, 3.0f}, {1, 2});
+ auto exp1 = ops::Exp(s.WithOpName("exp1").WithControlDependencies(x3), x1);
+ Output out1 = ops::Sub(s.WithOpName("out1"), exp1, x2);
+ Output out2 = ops::Sub(s.WithOpName("out2"), exp1, x3);
+
+ GrapplerItem item;
+ item.fetch = {"out1", "out2"};
+ TF_CHECK_OK(s.ToGraphDef(&item.graph));
+ auto tensors_expected = EvaluateNodes(item.graph, item.fetch);
+ EXPECT_EQ(2, tensors_expected.size());
+
+ GraphDef got;
+ ArithmeticOptimizer optimizer;
+ EnableOnlyExpm1(&optimizer);
+ OptimizeAndPrune(&optimizer, &item, &got);
+ auto tensors = EvaluateNodes(got, item.fetch);
+ EXPECT_EQ(2, tensors.size());
+
+ for (int i = 0; i < 2; ++i) {
+ EXPECT_EQ(tensors[i].NumElements(), tensors_expected[i].NumElements());
+ test::ExpectTensorNear<float>(tensors[i], tensors_expected[i], 1e-6);
+ }
+
+ GraphDef want;
+ AddNode("x1", "Const", {}, {}, &want);
+ AddNode("x2", "Const", {}, {}, &want);
+ AddNode("x3", "Const", {}, {}, &want);
+ AddNode("exp1", "Exp", {"x1", AsControlDependency("x3")}, {}, &want);
+ AddNode("out1", "Expm1",
+ {"x1", AsControlDependency("x2"), AsControlDependency("x3")}, {},
+ &want);
+ AddNode("out2", "Sub", {"exp1", "x3"}, {}, &want);
+
+ CompareGraphs(want, got);
+}
+
TEST_F(ArithmeticOptimizerTest, MinimizeBroadcasts_SimpleSwap) {
tensorflow::Scope s = tensorflow::Scope::NewRootScope();
diff --git a/tensorflow/core/grappler/optimizers/constant_folding.cc b/tensorflow/core/grappler/optimizers/constant_folding.cc
index 76c928f995..f2ac3a44c0 100644
--- a/tensorflow/core/grappler/optimizers/constant_folding.cc
+++ b/tensorflow/core/grappler/optimizers/constant_folding.cc
@@ -31,6 +31,7 @@ limitations under the License.
#include "tensorflow/core/grappler/costs/graph_properties.h"
#include "tensorflow/core/grappler/grappler_item.h"
#include "tensorflow/core/grappler/op_types.h"
+#include "tensorflow/core/grappler/optimizers/evaluation_utils.h"
#include "tensorflow/core/grappler/optimizers/symbolic_shapes.h"
#include "tensorflow/core/grappler/utils.h"
#include "tensorflow/core/lib/core/stringpiece.h"
@@ -73,44 +74,6 @@ class EigenThreadPoolWrapper : public Eigen::ThreadPoolInterface {
thread::ThreadPool* pool_ = nullptr;
};
-class DeviceSimple : public DeviceBase {
- public:
- DeviceSimple() : DeviceBase(Env::Default()) {
- eigen_worker_threads_.num_threads = port::NumSchedulableCPUs();
- eigen_worker_threads_.workers = new thread::ThreadPool(
- Env::Default(), "constant_folding", eigen_worker_threads_.num_threads);
- eigen_threadpool_wrapper_.reset(
- new EigenThreadPoolWrapper(eigen_worker_threads_.workers));
- eigen_device_.reset(new Eigen::ThreadPoolDevice(
- eigen_threadpool_wrapper_.get(), eigen_worker_threads_.num_threads));
- set_tensorflow_cpu_worker_threads(&eigen_worker_threads_);
- set_eigen_cpu_device(eigen_device_.get());
- }
- ~DeviceSimple() override {
- eigen_threadpool_wrapper_.reset();
- eigen_device_.reset();
- delete eigen_worker_threads_.workers;
- }
- Status MakeTensorFromProto(const TensorProto& tensor_proto,
- const AllocatorAttributes alloc_attrs,
- Tensor* tensor) override {
- Tensor parsed(tensor_proto.dtype());
- if (!parsed.FromProto(cpu_allocator(), tensor_proto)) {
- return errors::InvalidArgument("Cannot parse tensor from tensor_proto.");
- }
- *tensor = parsed;
- return Status::OK();
- }
- Allocator* GetAllocator(AllocatorAttributes attr) override {
- return cpu_allocator();
- }
-
- private:
- DeviceBase::CpuWorkerThreads eigen_worker_threads_;
- std::unique_ptr<Eigen::ThreadPoolInterface> eigen_threadpool_wrapper_;
- std::unique_ptr<Eigen::ThreadPoolDevice> eigen_device_;
-};
-
template <typename T>
bool AllValuesAre(const TensorProto& proto, const T& value) {
Tensor tensor;
@@ -983,33 +946,8 @@ Status ConstantFolding::CreateNodeDef(const string& name,
Status ConstantFolding::EvaluateNode(const NodeDef& node,
const TensorVector& inputs,
TensorVector* output) const {
- Status status;
- auto op_kernel =
- CreateOpKernel("CPU", cpu_device_, cpu_device_->GetAllocator({}), node,
- TF_GRAPH_DEF_VERSION, &status);
- TF_RETURN_IF_ERROR(status);
- OpKernelContext::Params params;
- params.device = cpu_device_;
- params.frame_iter = FrameAndIter(0, 0);
- params.inputs = &inputs;
- params.op_kernel = op_kernel.get();
- params.resource_manager = resource_mgr_.get();
-
- gtl::InlinedVector<AllocatorAttributes, 4> output_attrs;
- const int num_outputs = op_kernel->num_outputs();
- for (int i = 0; i < num_outputs; i++) {
- AllocatorAttributes attr;
- attr.set_on_host(true);
- output_attrs.push_back(attr);
- }
- params.output_attr_array = output_attrs.data();
-
- OpKernelContext op_context(&params);
- op_kernel->Compute(&op_context);
- for (int i = 0; i < num_outputs; i++) {
- output->push_back(op_context.release_output(i));
- }
- return op_context.status();
+ return ::tensorflow::grappler::EvaluateNode(node, inputs, cpu_device_,
+ resource_mgr_.get(), output);
}
Status ConstantFolding::EvaluateOneFoldable(const NodeDef& node,
@@ -1305,17 +1243,12 @@ Status ConstantFolding::FoldGraph(GraphDef* output) {
}
// Delete the newly created nodes that don't feed anything.
- int last = output->node_size() - 1;
- for (int i = output->node_size() - 1; i >= 0; --i) {
- const NodeDef& node = output->node(i);
- auto fanout = node_map_->GetOutputs(node.name());
- if (fanout.empty()) {
- output->mutable_node()->SwapElements(i, last);
- last--;
- }
+ std::vector<int> nodes_to_delete;
+ for (int i = 0; i < output->node_size(); i++) {
+ auto fanout = node_map_->GetOutputs(output->node(i).name());
+ if (fanout.empty()) nodes_to_delete.push_back(i);
}
- output->mutable_node()->DeleteSubrange(last + 1,
- output->node_size() - last - 1);
+ EraseNodesFromGraph(std::move(nodes_to_delete), output);
for (const auto& node : graph_->node()) {
// If no fetch nodes is provided, we conservatively
diff --git a/tensorflow/core/grappler/optimizers/data/BUILD b/tensorflow/core/grappler/optimizers/data/BUILD
index c8946c499c..d7ac58c99d 100644
--- a/tensorflow/core/grappler/optimizers/data/BUILD
+++ b/tensorflow/core/grappler/optimizers/data/BUILD
@@ -46,7 +46,7 @@ cc_library(
deps = [
"//tensorflow/core:framework",
"//tensorflow/core:lib",
- "//tensorflow/core/grappler:graph_view",
+ "//tensorflow/core/grappler:mutable_graph_view",
"//tensorflow/core/grappler:grappler_item",
"//tensorflow/core/grappler:utils",
] + tf_protos_all(),
@@ -58,8 +58,14 @@ tf_cc_test(
visibility = ["//visibility:public"],
deps = [
":graph_utils",
+ "//tensorflow/core:framework",
"//tensorflow/core:test",
"//tensorflow/core:test_main",
+ "//tensorflow/core:testlib",
+ "//tensorflow/core/grappler:grappler_item",
+ "//tensorflow/core/grappler/optimizers:custom_graph_optimizer",
+ "//tensorflow/core/grappler/optimizers:custom_graph_optimizer_registry",
+ "//tensorflow/core/kernels:cast_op",
],
)
@@ -73,7 +79,7 @@ cc_library(
deps = [
":graph_utils",
"//tensorflow/core:lib",
- "//tensorflow/core/grappler:graph_view",
+ "//tensorflow/core/grappler:mutable_graph_view",
"//tensorflow/core/grappler:grappler_item",
"//tensorflow/core/grappler:op_types",
"//tensorflow/core/grappler:utils",
@@ -98,6 +104,43 @@ tf_cc_test(
)
cc_library(
+ name = "map_fusion",
+ srcs = ["map_fusion.cc"],
+ hdrs = [
+ "map_fusion.h",
+ ],
+ visibility = ["//visibility:public"],
+ deps = [
+ ":graph_utils",
+ "//tensorflow/core/grappler:mutable_graph_view",
+ "//tensorflow/core:lib",
+ "//tensorflow/core/grappler:grappler_item",
+ "//tensorflow/core/grappler:op_types",
+ "//tensorflow/core/grappler:utils",
+ "//tensorflow/core/grappler/clusters:cluster",
+ "//tensorflow/core/kernels:cast_op",
+ "//tensorflow/core/grappler/utils:topological_sort",
+ "//tensorflow/core/grappler/optimizers:custom_graph_optimizer",
+ "//tensorflow/core/grappler/optimizers:custom_graph_optimizer_registry",
+ ] + tf_protos_all(),
+)
+
+tf_cc_test(
+ name = "map_fusion_test",
+ srcs = ["map_fusion_test.cc"],
+ visibility = ["//visibility:public"],
+ deps = [
+ ":graph_utils",
+ ":map_fusion",
+ "//tensorflow/core:framework",
+ "//tensorflow/core:test",
+ "//tensorflow/core:test_main",
+ "//tensorflow/core:testlib",
+ "//tensorflow/core/grappler:grappler_item",
+ ],
+)
+
+cc_library(
name = "noop_elimination",
srcs = ["noop_elimination.cc"],
hdrs = [
@@ -107,7 +150,7 @@ cc_library(
deps = [
":graph_utils",
"//tensorflow/core:lib",
- "//tensorflow/core/grappler:graph_view",
+ "//tensorflow/core/grappler:mutable_graph_view",
"//tensorflow/core/grappler:grappler_item",
"//tensorflow/core/grappler:op_types",
"//tensorflow/core/grappler:utils",
@@ -141,7 +184,7 @@ cc_library(
deps = [
":graph_utils",
"//tensorflow/core:lib",
- "//tensorflow/core/grappler:graph_view",
+ "//tensorflow/core/grappler:mutable_graph_view",
"//tensorflow/core/grappler:grappler_item",
"//tensorflow/core/grappler:op_types",
"//tensorflow/core/grappler:utils",
@@ -171,6 +214,7 @@ cc_library(
deps = [
":function_rename",
":map_and_batch_fusion",
+ ":map_fusion",
":noop_elimination",
":shuffle_and_repeat_fusion",
],
diff --git a/tensorflow/core/grappler/optimizers/data/graph_utils.cc b/tensorflow/core/grappler/optimizers/data/graph_utils.cc
index ea5f450009..6ce6533369 100644
--- a/tensorflow/core/grappler/optimizers/data/graph_utils.cc
+++ b/tensorflow/core/grappler/optimizers/data/graph_utils.cc
@@ -16,7 +16,7 @@ limitations under the License.
#include "tensorflow/core/grappler/optimizers/data/graph_utils.h"
#include "tensorflow/core/framework/device_base.h"
-#include "tensorflow/core/grappler/graph_view.h"
+#include "tensorflow/core/framework/op_def.pb.h"
#include "tensorflow/core/util/ptr_util.h"
namespace tensorflow {
@@ -26,14 +26,12 @@ namespace {
constexpr char kConstOpName[] = "Const";
-int FindNodeWithPredicate(const std::function<bool(const NodeDef&)>& predicate,
- const GraphDef& graph) {
- for (int i = 0; i < graph.node_size(); ++i) {
- if (predicate(graph.node(i))) {
- return i;
- }
- }
- return -1;
+template <typename Predicate, typename Collection>
+int GetElementIdxWithPredicate(const Predicate& predicate,
+ const Collection& collection) {
+ auto it = std::find_if(collection.begin(), collection.end(), predicate);
+ if (it == collection.end()) return -1;
+ return std::distance(collection.begin(), it);
}
std::vector<int> CreateNameIndex(const GraphDef& graph) {
@@ -62,13 +60,14 @@ std::vector<int> CreateInputIndex(const NodeDef& node) {
return index;
}
-Status AddScalarConstNodeHelper(
+NodeDef* AddScalarConstNodeHelper(
DataType dtype, const std::function<void(TensorProto*)>& add_value,
- GraphDef* graph, NodeDef** result) {
- NodeDef* node = graph->add_node();
- node->set_op(kConstOpName);
- SetUniqueName(kConstOpName, graph, node);
- (*node->mutable_attr())["dtype"].set_type(dtype);
+ MutableGraphView* graph) {
+ NodeDef node;
+ node.set_op(kConstOpName);
+ SetUniqueGraphNodeName(kConstOpName, graph->GetGraph(), &node);
+
+ (*node.mutable_attr())["dtype"].set_type(dtype);
std::unique_ptr<tensorflow::TensorProto> tensor =
tensorflow::MakeUnique<tensorflow::TensorProto>();
std::unique_ptr<tensorflow::TensorShapeProto> tensor_shape =
@@ -76,75 +75,69 @@ Status AddScalarConstNodeHelper(
tensor->set_allocated_tensor_shape(tensor_shape.release());
tensor->set_dtype(dtype);
add_value(tensor.get());
- (*node->mutable_attr())["value"].set_allocated_tensor(tensor.release());
- *result = node;
- return Status::OK();
+ (*node.mutable_attr())["value"].set_allocated_tensor(tensor.release());
+
+ return graph->AddNode(std::move(node));
}
} // namespace
-Status AddNode(const string& name, const string& op,
- const std::vector<string>& inputs,
- const std::vector<std::pair<string, AttrValue>>& attributes,
- GraphDef* graph, NodeDef** result) {
- NodeDef* node = graph->add_node();
+NodeDef* AddNode(const string& name, const string& op,
+ const std::vector<string>& inputs,
+ const std::vector<std::pair<string, AttrValue>>& attributes,
+ MutableGraphView* graph) {
+ NodeDef node;
if (!name.empty()) {
- node->set_name(name);
+ node.set_name(name);
} else {
- SetUniqueName(op, graph, node);
+ SetUniqueGraphNodeName(op, graph->GetGraph(), &node);
}
- node->set_op(op);
+ node.set_op(op);
for (const string& input : inputs) {
- node->add_input(input);
+ node.add_input(input);
}
for (auto attr : attributes) {
- (*node->mutable_attr())[attr.first] = attr.second;
+ (*node.mutable_attr())[attr.first] = attr.second;
}
- *result = node;
- return Status::OK();
+ return graph->AddNode(std::move(node));
}
template <>
-Status AddScalarConstNode(bool v, GraphDef* graph, NodeDef** result) {
+NodeDef* AddScalarConstNode(bool v, MutableGraphView* graph) {
return AddScalarConstNodeHelper(
- DT_BOOL, [v](TensorProto* proto) { proto->add_bool_val(v); }, graph,
- result);
+ DT_BOOL, [v](TensorProto* proto) { proto->add_bool_val(v); }, graph);
}
template <>
-Status AddScalarConstNode(double v, GraphDef* graph, NodeDef** result) {
+NodeDef* AddScalarConstNode(double v, MutableGraphView* graph) {
return AddScalarConstNodeHelper(
- DT_DOUBLE, [v](TensorProto* proto) { proto->add_double_val(v); }, graph,
- result);
+ DT_DOUBLE, [v](TensorProto* proto) { proto->add_double_val(v); }, graph);
}
template <>
-Status AddScalarConstNode(float v, GraphDef* graph, NodeDef** result) {
+NodeDef* AddScalarConstNode(float v, MutableGraphView* graph) {
return AddScalarConstNodeHelper(
- DT_FLOAT, [v](TensorProto* proto) { proto->add_float_val(v); }, graph,
- result);
+ DT_FLOAT, [v](TensorProto* proto) { proto->add_float_val(v); }, graph);
}
template <>
-Status AddScalarConstNode(int v, GraphDef* graph, NodeDef** result) {
+NodeDef* AddScalarConstNode(int v, MutableGraphView* graph) {
return AddScalarConstNodeHelper(
- DT_INT32, [v](TensorProto* proto) { proto->add_int_val(v); }, graph,
- result);
+ DT_INT32, [v](TensorProto* proto) { proto->add_int_val(v); }, graph);
}
template <>
-Status AddScalarConstNode(int64 v, GraphDef* graph, NodeDef** result) {
+NodeDef* AddScalarConstNode(int64 v, MutableGraphView* graph) {
return AddScalarConstNodeHelper(
- DT_INT64, [v](TensorProto* proto) { proto->add_int64_val(v); }, graph,
- result);
+ DT_INT64, [v](TensorProto* proto) { proto->add_int64_val(v); }, graph);
}
template <>
-Status AddScalarConstNode(StringPiece v, GraphDef* graph, NodeDef** result) {
+NodeDef* AddScalarConstNode(StringPiece v, MutableGraphView* graph) {
return AddScalarConstNodeHelper(
DT_STRING,
[v](TensorProto* proto) { proto->add_string_val(v.data(), v.size()); },
- graph, result);
+ graph);
}
bool Compare(const GraphDef& g1, const GraphDef& g2) {
@@ -177,52 +170,82 @@ bool Compare(const GraphDef& g1, const GraphDef& g2) {
return true;
}
-bool ContainsNodeWithName(const string& name, const GraphDef& graph) {
- return FindNodeWithName(name, graph) != -1;
+bool ContainsGraphNodeWithName(const string& name, const GraphDef& graph) {
+ return FindGraphNodeWithName(name, graph) != -1;
}
bool ContainsNodeWithOp(const string& op, const GraphDef& graph) {
return FindNodeWithOp(op, graph) != -1;
}
-Status DeleteNodes(const std::set<string>& nodes_to_delete, GraphDef* graph) {
- int last = graph->node_size() - 1;
- for (int i = graph->node_size() - 1; i >= 0; --i) {
- const NodeDef& node = graph->node(i);
- if (nodes_to_delete.find(node.name()) != nodes_to_delete.end()) {
- graph->mutable_node()->SwapElements(i, last);
- last--;
- }
- }
- graph->mutable_node()->DeleteSubrange(last + 1,
- graph->node_size() - last - 1);
- return Status::OK();
+bool ContainsGraphFunctionWithName(const string& name,
+ const FunctionDefLibrary& library) {
+ return FindGraphFunctionWithName(name, library) != -1;
}
-int FindNodeWithName(const string& name, const GraphDef& graph) {
- return FindNodeWithPredicate(
- [name](const NodeDef& node) { return node.name() == name; }, graph);
+bool ContainsFunctionNodeWithName(const string& name,
+ const FunctionDef& function) {
+ return FindFunctionNodeWithName(name, function) != -1;
+}
+
+int FindGraphNodeWithName(const string& name, const GraphDef& graph) {
+ return GetElementIdxWithPredicate(
+ [&name](const NodeDef& node) { return node.name() == name; },
+ graph.node());
}
int FindNodeWithOp(const string& op, const GraphDef& graph) {
- return FindNodeWithPredicate(
- [op](const NodeDef& node) { return node.op() == op; }, graph);
+ return GetElementIdxWithPredicate(
+ [&op](const NodeDef& node) { return node.op() == op; }, graph.node());
}
-void SetUniqueName(const string& op, GraphDef* graph, NodeDef* node) {
+int FindGraphFunctionWithName(const string& name,
+ const FunctionDefLibrary& library) {
+ return GetElementIdxWithPredicate(
+ [&name](const FunctionDef& function) {
+ return function.signature().name() == name;
+ },
+ library.function());
+}
+
+int FindFunctionNodeWithName(const string& name, const FunctionDef& function) {
+ return GetElementIdxWithPredicate(
+ [&name](const NodeDef& node) { return node.name() == name; },
+ function.node_def());
+}
+
+void SetUniqueGraphNodeName(const string& prefix, GraphDef* graph,
+ NodeDef* node) {
+ string name = prefix;
int id = graph->node_size();
- while (ContainsNodeWithName(strings::StrCat(op, "/_", id), *graph)) {
+ while (ContainsGraphNodeWithName(name, *graph)) {
+ name = strings::StrCat(prefix, "/_", id);
++id;
}
- node->set_name(strings::StrCat(op, "/_", id));
+ node->set_name(std::move(name));
}
-void ReplaceInput(const NodeDef& old_input, const NodeDef& new_input,
- GraphView* graph) {
- GraphView::OutputPort output_port = graph->GetOutputPort(old_input.name(), 0);
- auto fanout = graph->GetFanout(output_port);
- for (auto& input_port : fanout)
- input_port.node->set_input(0, new_input.name());
+void SetUniqueFunctionNodeName(const string& prefix, FunctionDef* function,
+ NodeDef* node) {
+ string name = prefix;
+ int id = function->node_def_size();
+ while (ContainsFunctionNodeWithName(name, *function)) {
+ name = strings::StrCat(prefix, "/_", id);
+ ++id;
+ }
+ node->set_name(std::move(name));
+}
+
+void SetUniqueGraphFunctionName(const string& prefix,
+ FunctionDefLibrary* library,
+ FunctionDef* function) {
+ string name = prefix;
+ int id = library->function_size();
+ while (ContainsGraphFunctionWithName(name, *library)) {
+ name = strings::StrCat(prefix, "/_", id);
+ ++id;
+ }
+ function->mutable_signature()->set_name(name);
}
} // end namespace graph_utils
diff --git a/tensorflow/core/grappler/optimizers/data/graph_utils.h b/tensorflow/core/grappler/optimizers/data/graph_utils.h
index 1cb0f0c81d..0847748802 100644
--- a/tensorflow/core/grappler/optimizers/data/graph_utils.h
+++ b/tensorflow/core/grappler/optimizers/data/graph_utils.h
@@ -17,12 +17,13 @@ limitations under the License.
#define TENSORFLOW_CORE_GRAPPLER_OPTIMIZERS_DATA_GRAPH_UTILS_H_
#include "tensorflow/core/framework/attr_value.pb.h"
+#include "tensorflow/core/framework/function.pb.h"
#include "tensorflow/core/framework/graph.pb.h"
#include "tensorflow/core/framework/node_def.pb.h"
#include "tensorflow/core/framework/tensor.pb.h"
#include "tensorflow/core/framework/tensor_shape.pb.h"
#include "tensorflow/core/framework/types.h"
-#include "tensorflow/core/grappler/graph_view.h"
+#include "tensorflow/core/grappler/mutable_graph_view.h"
#include "tensorflow/core/grappler/utils.h"
#include "tensorflow/core/lib/core/errors.h"
@@ -31,57 +32,83 @@ namespace grappler {
namespace graph_utils {
// Adds a node to the graph.
-Status AddNode(const string& name, const string& op,
- const std::vector<string>& inputs,
- const std::vector<std::pair<string, AttrValue>>& attributes,
- GraphDef* graph, NodeDef** result);
+NodeDef* AddNode(const string& name, const string& op,
+ const std::vector<string>& inputs,
+ const std::vector<std::pair<string, AttrValue>>& attributes,
+ MutableGraphView* graph);
// Adds a Const node with the given value to the graph.
template <typename T>
-Status AddScalarConstNode(T v, GraphDef* graph, NodeDef** result) {
- return errors::Unimplemented("Type %s is not supported.",
- DataTypeToEnum<T>::value);
+NodeDef* AddScalarConstNode(T v, MutableGraphView* graph) {
+ // is_same is an idiomatic hack for making it compile if not instantiated.
+ // Replacing with false will result in a compile-time error.
+ static_assert(!std::is_same<T, T>::value,
+ "Invalid specialization of this method for type T.");
+ return {};
}
+
template <>
-Status AddScalarConstNode(bool v, GraphDef* graph, NodeDef** result);
+NodeDef* AddScalarConstNode(bool v, MutableGraphView* graph);
template <>
-Status AddScalarConstNode(double v, GraphDef* graph, NodeDef** result);
+NodeDef* AddScalarConstNode(double v, MutableGraphView* graph);
template <>
-Status AddScalarConstNode(float v, GraphDef* graph, NodeDef** result);
+NodeDef* AddScalarConstNode(float v, MutableGraphView* graph);
template <>
-Status AddScalarConstNode(int v, GraphDef* graph, NodeDef** result);
+NodeDef* AddScalarConstNode(int v, MutableGraphView* graph);
template <>
-Status AddScalarConstNode(int64 v, GraphDef* graph, NodeDef** result);
+NodeDef* AddScalarConstNode(int64 v, MutableGraphView* graph);
template <>
-Status AddScalarConstNode(StringPiece v, GraphDef* graph, NodeDef** result);
+NodeDef* AddScalarConstNode(StringPiece v, MutableGraphView* graph);
// Checks whether the two graphs are the same.
bool Compare(const GraphDef& g1, const GraphDef& g2);
// Checks whether the graph contains a node with the given name.
-bool ContainsNodeWithName(const string& name, const GraphDef& graph);
+bool ContainsGraphNodeWithName(const string& name, const GraphDef& graph);
+
+// Checks whether the library contains a function with the given name.
+bool ContainsGraphFunctionWithName(const string& name,
+ const FunctionDefLibrary& library);
+
+// Checks whether the function contains a node with the given name.
+bool ContainsFunctionNodeWithName(const string& name,
+ const FunctionDef& function);
// Checks whether the graph contains a node with the given op.
bool ContainsNodeWithOp(const string& op, const GraphDef& graph);
-// Deletes nodes from the graph.
-Status DeleteNodes(const std::set<string>& nodes_to_delete, GraphDef* graph);
-
// Returns the index of the node with the given name or -1 if the node does
// not exist.
-int FindNodeWithName(const string& name, const GraphDef& graph);
+int FindGraphNodeWithName(const string& name, const GraphDef& graph);
+
+// Returns the index of the function with the given name or -1 if the function
+// does not exist.
+int FindGraphFunctionWithName(const string& name,
+ const FunctionDefLibrary& library);
+
+// Returns the index of the function node with the given name or -1 if the
+// function node does not exist.
+int FindFunctionNodeWithName(const string& name, const FunctionDef& function);
// Returns the index of a node with the given op or -1 if no such node
// exists.
int FindNodeWithOp(const string& op, const GraphDef& graph);
-// Sets the node name using the op name as a prefix while guaranteeing the name
+// Sets the node name using `prefix` as a prefix while guaranteeing the name
// is unique across the graph.
-void SetUniqueName(const string& op, GraphDef* graph, NodeDef* node);
-
-// Replaces the input for the output nodes of 'old_input' with 'new_input'.
-void ReplaceInput(const NodeDef& old_input, const NodeDef& new_input,
- GraphView* graph);
+void SetUniqueGraphNodeName(const string& prefix, GraphDef* graph,
+ NodeDef* node);
+
+// Sets the function node name using the `prefix` as a prefix while guaranteeing
+// the name is unique across the functions nodes.
+void SetUniqueFunctionNodeName(const string& prefix, FunctionDef* function,
+ NodeDef* node);
+
+// Sets the node name using the `prefix` name as a prefix while guaranteeing the
+// name is unique across the graph.
+void SetUniqueGraphFunctionName(const string& prefix,
+ FunctionDefLibrary* library,
+ FunctionDef* function);
} // end namespace graph_utils
} // end namespace grappler
diff --git a/tensorflow/core/grappler/optimizers/data/graph_utils_test.cc b/tensorflow/core/grappler/optimizers/data/graph_utils_test.cc
index d723d73b7a..59ed79ab8f 100644
--- a/tensorflow/core/grappler/optimizers/data/graph_utils_test.cc
+++ b/tensorflow/core/grappler/optimizers/data/graph_utils_test.cc
@@ -15,6 +15,7 @@ limitations under the License.
#include "tensorflow/core/grappler/optimizers/data/graph_utils.h"
+#include "tensorflow/core/framework/function_testlib.h"
#include "tensorflow/core/lib/core/status_test_util.h"
#include "tensorflow/core/platform/test.h"
@@ -24,149 +25,191 @@ namespace graph_utils {
namespace {
TEST(GraphUtilsTest, AddScalarConstNodeBool) {
- GraphDef graph;
- NodeDef* bool_node;
- TF_EXPECT_OK(AddScalarConstNode<bool>(true, &graph, &bool_node));
- EXPECT_TRUE(ContainsNodeWithName(bool_node->name(), graph));
+ GraphDef graph_def;
+ MutableGraphView graph(&graph_def);
+ NodeDef* bool_node = AddScalarConstNode<bool>(true, &graph);
+ EXPECT_TRUE(ContainsGraphNodeWithName(bool_node->name(), *graph.GetGraph()));
EXPECT_EQ(bool_node->attr().at("value").tensor().bool_val(0), true);
}
TEST(GraphUtilsTest, AddScalarConstNodeDouble) {
- GraphDef graph;
- NodeDef* double_node;
- TF_EXPECT_OK(AddScalarConstNode<double>(3.14, &graph, &double_node));
- EXPECT_TRUE(ContainsNodeWithName(double_node->name(), graph));
+ GraphDef graph_def;
+ MutableGraphView graph(&graph_def);
+ NodeDef* double_node = AddScalarConstNode<double>(3.14, &graph);
+ EXPECT_TRUE(
+ ContainsGraphNodeWithName(double_node->name(), *graph.GetGraph()));
EXPECT_FLOAT_EQ(double_node->attr().at("value").tensor().double_val(0), 3.14);
}
TEST(GraphUtilsTest, AddScalarConstNodeFloat) {
- GraphDef graph;
- NodeDef* float_node;
- TF_EXPECT_OK(AddScalarConstNode<float>(3.14, &graph, &float_node));
- EXPECT_TRUE(ContainsNodeWithName(float_node->name(), graph));
+ GraphDef graph_def;
+ MutableGraphView graph(&graph_def);
+ NodeDef* float_node = AddScalarConstNode<float>(3.14, &graph);
+ EXPECT_TRUE(ContainsGraphNodeWithName(float_node->name(), *graph.GetGraph()));
EXPECT_FLOAT_EQ(float_node->attr().at("value").tensor().float_val(0), 3.14);
}
TEST(GraphUtilsTest, AddScalarConstNodeInt) {
- GraphDef graph;
- NodeDef* int_node;
- TF_EXPECT_OK(AddScalarConstNode<int>(42, &graph, &int_node));
- EXPECT_TRUE(ContainsNodeWithName(int_node->name(), graph));
+ GraphDef graph_def;
+ MutableGraphView graph(&graph_def);
+ NodeDef* int_node = AddScalarConstNode<int>(42, &graph);
+ EXPECT_TRUE(ContainsGraphNodeWithName(int_node->name(), *graph.GetGraph()));
EXPECT_EQ(int_node->attr().at("value").tensor().int_val(0), 42);
}
TEST(GraphUtilsTest, AddScalarConstNodeInt64) {
- GraphDef graph;
- NodeDef* int64_node;
- TF_EXPECT_OK(AddScalarConstNode<int64>(42, &graph, &int64_node));
- EXPECT_TRUE(ContainsNodeWithName(int64_node->name(), graph));
+ GraphDef graph_def;
+ MutableGraphView graph(&graph_def);
+ NodeDef* int64_node = AddScalarConstNode<int64>(42, &graph);
+ EXPECT_TRUE(ContainsGraphNodeWithName(int64_node->name(), *graph.GetGraph()));
EXPECT_EQ(int64_node->attr().at("value").tensor().int64_val(0), 42);
}
TEST(GraphUtilsTest, AddScalarConstNodeString) {
- GraphDef graph;
- NodeDef* string_node;
- TF_EXPECT_OK(AddScalarConstNode<StringPiece>("hello", &graph, &string_node));
- EXPECT_TRUE(ContainsNodeWithName(string_node->name(), graph));
+ GraphDef graph_def;
+ MutableGraphView graph(&graph_def);
+ NodeDef* string_node = AddScalarConstNode<StringPiece>("hello", &graph);
+ EXPECT_TRUE(
+ ContainsGraphNodeWithName(string_node->name(), *graph.GetGraph()));
EXPECT_EQ(string_node->attr().at("value").tensor().string_val(0), "hello");
}
TEST(GraphUtilsTest, Compare) {
- GraphDef graphA;
- GraphDef graphB;
- EXPECT_TRUE(Compare(graphA, graphB));
+ GraphDef graph_def_a;
+ MutableGraphView graph_a(&graph_def_a);
+ GraphDef graph_def_b;
+ MutableGraphView graph_b(&graph_def_b);
- NodeDef* nodeA;
- TF_EXPECT_OK(AddNode("A", "OpA", {}, {}, &graphA, &nodeA));
- NodeDef* nodeB;
- TF_EXPECT_OK(AddNode("B", "OpB", {"A"}, {}, &graphA, &nodeB));
- EXPECT_FALSE(Compare(graphA, graphB));
+ EXPECT_TRUE(Compare(graph_def_a, graph_def_b));
- graphB.mutable_node()->CopyFrom(graphA.node());
- EXPECT_TRUE(Compare(graphA, graphB));
+ AddNode("A", "OpA", {}, {}, &graph_a);
+ AddNode("B", "OpB", {"A"}, {}, &graph_a);
+ EXPECT_FALSE(Compare(graph_def_a, graph_def_b));
+
+ graph_def_b.mutable_node()->CopyFrom(graph_def_a.node());
+ EXPECT_TRUE(Compare(graph_def_a, graph_def_b));
}
-TEST(GraphUtilsTest, ContainsNodeWithName) {
- GraphDef graph;
- EXPECT_TRUE(!ContainsNodeWithName("A", graph));
+TEST(GraphUtilsTest, ContainsGraphNodeWithName) {
+ GraphDef graph_def;
+ MutableGraphView graph(&graph_def);
+ EXPECT_TRUE(!ContainsGraphNodeWithName("A", *graph.GetGraph()));
- NodeDef* node;
- TF_EXPECT_OK(AddNode("A", "OpA", {}, {}, &graph, &node));
- EXPECT_TRUE(ContainsNodeWithName("A", graph));
+ AddNode("A", "OpA", {}, {}, &graph);
+ EXPECT_TRUE(ContainsGraphNodeWithName("A", *graph.GetGraph()));
- TF_EXPECT_OK(DeleteNodes({"A"}, &graph));
- EXPECT_TRUE(!ContainsNodeWithName("A", graph));
+ graph.DeleteNodes({"A"});
+ EXPECT_TRUE(!ContainsGraphNodeWithName("A", *graph.GetGraph()));
}
-TEST(GraphUtilsTest, ContainsNodeWithOp) {
- GraphDef graph;
- EXPECT_TRUE(!ContainsNodeWithOp("OpA", graph));
+TEST(GraphUtilsTest, ContainsGraphFunctionWithName) {
+ FunctionDefLibrary library;
+ EXPECT_FALSE(ContainsGraphFunctionWithName("new_function", library));
+ FunctionDef* new_function = library.add_function();
+ SetUniqueGraphFunctionName("new_function", &library, new_function);
- NodeDef* node;
- TF_EXPECT_OK(AddNode("A", "OpA", {}, {}, &graph, &node));
- EXPECT_TRUE(ContainsNodeWithOp("OpA", graph));
+ EXPECT_TRUE(
+ ContainsGraphFunctionWithName(new_function->signature().name(), library));
+}
- TF_EXPECT_OK(DeleteNodes({"A"}, &graph));
- EXPECT_TRUE(!ContainsNodeWithOp("OpA", graph));
+TEST(GraphUtilsTest, ContainsFunctionNodeWithName) {
+ FunctionDef function = test::function::XTimesTwo();
+ EXPECT_FALSE(ContainsFunctionNodeWithName(
+ "weird_name_that_should_not_be_there", function));
+ EXPECT_TRUE(ContainsFunctionNodeWithName("two", function));
}
-TEST(GraphUtilsTest, FindNodeWithName) {
- GraphDef graph;
- EXPECT_EQ(FindNodeWithName("A", graph), -1);
+TEST(GraphUtilsTest, ContainsNodeWithOp) {
+ GraphDef graph_def;
+ MutableGraphView graph(&graph_def);
+ EXPECT_TRUE(!ContainsNodeWithOp("OpA", *graph.GetGraph()));
- NodeDef* node;
- TF_EXPECT_OK(AddNode("A", "OpA", {}, {}, &graph, &node));
- EXPECT_NE(FindNodeWithName("A", graph), -1);
+ AddNode("A", "OpA", {}, {}, &graph);
+ EXPECT_TRUE(ContainsNodeWithOp("OpA", *graph.GetGraph()));
- TF_EXPECT_OK(DeleteNodes({"A"}, &graph));
- EXPECT_EQ(FindNodeWithName("A", graph), -1);
+ graph.DeleteNodes({"A"});
+ EXPECT_TRUE(!ContainsNodeWithOp("OpA", *graph.GetGraph()));
}
-TEST(GraphUtilsTest, FindNodeWithOp) {
- GraphDef graph;
- EXPECT_EQ(FindNodeWithOp("OpA", graph), -1);
+TEST(GraphUtilsTest, FindGraphNodeWithName) {
+ GraphDef graph_def;
+ MutableGraphView graph(&graph_def);
+ EXPECT_EQ(FindGraphNodeWithName("A", *graph.GetGraph()), -1);
- NodeDef* node;
- TF_EXPECT_OK(AddNode("A", "OpA", {}, {}, &graph, &node));
- EXPECT_NE(FindNodeWithOp("OpA", graph), -1);
+ AddNode("A", "OpA", {}, {}, &graph);
+ EXPECT_NE(FindGraphNodeWithName("A", *graph.GetGraph()), -1);
- TF_EXPECT_OK(DeleteNodes({"A"}, &graph));
- EXPECT_EQ(FindNodeWithOp("OpA", graph), -1);
+ graph.DeleteNodes({"A"});
+ EXPECT_EQ(FindGraphNodeWithName("A", *graph.GetGraph()), -1);
}
-TEST(GraphUtilsTest, SetUniqueName) {
- GraphDef graph;
+TEST(GraphUtilsTest, FindFunctionWithName) {
+ FunctionDef function = test::function::XTimesTwo();
+ EXPECT_EQ(
+ FindFunctionNodeWithName("weird_name_that_should_not_be_there", function),
+ -1);
+ EXPECT_NE(FindFunctionNodeWithName("two", function), -1);
+}
- NodeDef* node1;
- TF_EXPECT_OK(AddNode("", "A", {}, {}, &graph, &node1));
- NodeDef* node2;
- TF_EXPECT_OK(AddNode("", "A", {}, {}, &graph, &node2));
- EXPECT_NE(node1->name(), node2->name());
+TEST(GraphUtilsTest, FindGraphFunctionWithName) {
+ FunctionDefLibrary library;
+ EXPECT_EQ(FindGraphFunctionWithName("new_function", library), -1);
+ FunctionDef* new_function = library.add_function();
+ SetUniqueGraphFunctionName("new_function", &library, new_function);
- TF_EXPECT_OK(DeleteNodes({node1->name()}, &graph));
- NodeDef* node3;
- TF_EXPECT_OK(AddNode("", "A", {}, {}, &graph, &node3));
- EXPECT_NE(node2->name(), node3->name());
+ EXPECT_NE(
+ FindGraphFunctionWithName(new_function->signature().name(), library), -1);
}
-TEST(GraphUtilsTest, ReplaceInput) {
- GraphDef graph;
+TEST(GraphUtilsTest, FindNodeWithOp) {
+ GraphDef graph_def;
+ MutableGraphView graph(&graph_def);
+ EXPECT_EQ(FindNodeWithOp("OpA", *graph.GetGraph()), -1);
+
+ AddNode("A", "OpA", {}, {}, &graph);
+ EXPECT_NE(FindNodeWithOp("OpA", *graph.GetGraph()), -1);
+
+ graph.DeleteNodes({"A"});
+ EXPECT_EQ(FindNodeWithOp("OpA", *graph.GetGraph()), -1);
+}
- NodeDef* node1;
- TF_EXPECT_OK(AddNode("", "A", {}, {}, &graph, &node1));
+TEST(GraphUtilsTest, SetUniqueGraphNodeName) {
+ GraphDef graph_def;
+ MutableGraphView graph(&graph_def);
- NodeDef* node2;
- TF_EXPECT_OK(AddNode("", "A", {node1->name()}, {}, &graph, &node2));
+ NodeDef* node1 = AddNode("", "A", {}, {}, &graph);
+ NodeDef* node2 = AddNode("", "A", {}, {}, &graph);
+ EXPECT_NE(node1->name(), node2->name());
- NodeDef* node3;
- TF_EXPECT_OK(AddNode("", "A", {node2->name()}, {}, &graph, &node3));
+ graph.DeleteNodes({node1->name()});
+ NodeDef* node3 = AddNode("", "A", {}, {}, &graph);
+ EXPECT_NE(node2->name(), node3->name());
+}
- EXPECT_EQ(node3->input(0), node2->name());
+TEST(GraphUtilsTest, SetUniqueFunctionNodeName) {
+ FunctionDef function = test::function::XTimesTwo();
+ NodeDef node;
+ SetUniqueFunctionNodeName("abc", &function, &node);
+ for (const NodeDef& function_node : function.node_def()) {
+ EXPECT_NE(node.name(), function_node.name());
+ }
+ auto* new_node = function.add_node_def();
+ *new_node = node;
+
+ NodeDef other;
+ SetUniqueFunctionNodeName("abc", &function, &other);
+ EXPECT_NE(other.name(), new_node->name());
+}
- GraphView view(&graph);
- ReplaceInput(*node2, *node1, &view);
+TEST(GraphUtilsTest, SetUniqueGraphFunctionName) {
+ FunctionDefLibrary library;
+ FunctionDef* new_function = library.add_function();
+ SetUniqueGraphFunctionName("new_function", &library, new_function);
- EXPECT_EQ(node3->input(0), node1->name());
+ FunctionDef* other_function = library.add_function();
+ SetUniqueGraphFunctionName("new_function", &library, other_function);
+ EXPECT_NE(new_function->signature().name(),
+ other_function->signature().name());
}
} // namespace
diff --git a/tensorflow/core/grappler/optimizers/data/map_and_batch_fusion.cc b/tensorflow/core/grappler/optimizers/data/map_and_batch_fusion.cc
index eac665bd92..3ce238a30a 100644
--- a/tensorflow/core/grappler/optimizers/data/map_and_batch_fusion.cc
+++ b/tensorflow/core/grappler/optimizers/data/map_and_batch_fusion.cc
@@ -18,8 +18,8 @@ limitations under the License.
#include "tensorflow/core/framework/attr_value.pb.h"
#include "tensorflow/core/framework/node_def.pb.h"
#include "tensorflow/core/grappler/clusters/cluster.h"
-#include "tensorflow/core/grappler/graph_view.h"
#include "tensorflow/core/grappler/grappler_item.h"
+#include "tensorflow/core/grappler/mutable_graph_view.h"
#include "tensorflow/core/grappler/op_types.h"
#include "tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.h"
#include "tensorflow/core/grappler/optimizers/data/graph_utils.h"
@@ -32,12 +32,70 @@ namespace {
constexpr char kFusedOpName[] = "MapAndBatchDatasetV2";
+NodeDef make_map_and_batch_node(const NodeDef& map_node,
+ const NodeDef& batch_node,
+ MutableGraphView* graph) {
+ NodeDef new_node;
+ new_node.set_op(kFusedOpName);
+ graph_utils::SetUniqueGraphNodeName(kFusedOpName, graph->GetGraph(),
+ &new_node);
+
+ // Set the `input` input argument.
+ new_node.add_input(map_node.input(0));
+
+ // Set the `other_arguments` input arguments.
+ int num_other_args;
+ if (map_node.op() == "ParallelMapDataset") {
+ num_other_args = map_node.input_size() - 2;
+ } else {
+ num_other_args = map_node.input_size() - 1;
+ }
+ for (int i = 0; i < num_other_args; i++) {
+ new_node.add_input(map_node.input(i + 1));
+ }
+
+ // Set the `batch_size` input argument.
+ new_node.add_input(batch_node.input(1));
+
+ // Set the `num_parallel_calls` input argument.
+ if (map_node.op() == "ParallelMapDataset") {
+ // The type of the `num_parallel_calls` argument in ParallelMapDataset
+ // and MapAndBatchDataset is different (int32 and int64 respectively)
+ // so we cannot reuse the same Const node and thus create a new one.
+ NodeDef* v = graph->GetNode(map_node.input(map_node.input_size() - 1));
+ NodeDef* tmp = graph_utils::AddScalarConstNode<int64>(
+ v->attr().at("value").tensor().int_val(0), graph);
+ new_node.add_input(tmp->name());
+ } else {
+ NodeDef* tmp = graph_utils::AddScalarConstNode<int64>(1, graph);
+ new_node.add_input(tmp->name());
+ }
+
+ // Set the `drop_remainder` input argument.
+ if (batch_node.op() == "BatchDatasetV2") {
+ new_node.add_input(batch_node.input(2));
+ } else {
+ NodeDef* tmp = graph_utils::AddScalarConstNode<bool>(false, graph);
+ new_node.add_input(tmp->name());
+ }
+
+ // Set `f` and `Targuments` attributes.
+ for (auto key : {"f", "Targuments"}) {
+ (*new_node.mutable_attr())[key] = map_node.attr().at(key);
+ }
+ // Set `output_types` and `output_shapes` attributes.
+ for (auto key : {"output_shapes", "output_types"}) {
+ (*new_node.mutable_attr())[key] = batch_node.attr().at(key);
+ }
+ return new_node;
+}
+
} // namespace
Status MapAndBatchFusion::Optimize(Cluster* cluster, const GrapplerItem& item,
GraphDef* output) {
*output = item.graph;
- GraphView graph(output);
+ MutableGraphView graph(output);
std::set<string> nodes_to_delete;
for (const NodeDef& node : item.graph.node()) {
if (node.op() != "BatchDataset" && node.op() != "BatchDatasetV2") {
@@ -45,79 +103,25 @@ Status MapAndBatchFusion::Optimize(Cluster* cluster, const GrapplerItem& item,
}
// Use a more descriptive variable name now that we know the node type.
- const NodeDef batch_node(node);
+ const NodeDef& batch_node = node;
GraphView::InputPort input_port = graph.GetInputPort(batch_node.name(), 0);
NodeDef* node2 = graph.GetRegularFanin(input_port).node;
if (node2->op() != "MapDataset" && node2->op() != "ParallelMapDataset") {
continue;
}
-
- NodeDef* new_node = output->add_node();
- new_node->set_op(kFusedOpName);
- graph_utils::SetUniqueName(kFusedOpName, output, new_node);
-
// Use a more descriptive variable name now that we know the node type.
NodeDef* map_node = node2;
- // Set the `input` input argument.
- new_node->add_input(map_node->input(0));
-
- // Set the `other_arguments` input arguments.
- int num_other_args;
- if (map_node->op() == "ParallelMapDataset") {
- num_other_args = map_node->input_size() - 2;
- } else {
- num_other_args = map_node->input_size() - 1;
- }
- for (int i = 0; i < num_other_args; i++) {
- new_node->add_input(map_node->input(i + 1));
- }
- // Set the `batch_size` input argument.
- new_node->add_input(batch_node.input(1));
-
- // Set the `num_parallel_calls` input argument.
- if (map_node->op() == "ParallelMapDataset") {
- // The type of the `num_parallel_calls` argument in ParallelMapDataset
- // and MapAndBatchDataset is different (int32 and int64 respectively)
- // so we cannot reuse the same Const node and thus create a new one.
- NodeDef* v = graph.GetNode(map_node->input(map_node->input_size() - 1));
- NodeDef* tmp;
- TF_RETURN_IF_ERROR(graph_utils::AddScalarConstNode<int64>(
- v->attr().at("value").tensor().int_val(0), output, &tmp));
- new_node->add_input(tmp->name());
- } else {
- NodeDef* tmp;
- TF_RETURN_IF_ERROR(
- graph_utils::AddScalarConstNode<int64>(1, output, &tmp));
- new_node->add_input(tmp->name());
- }
-
- // Set the `drop_remainder` input argument.
- if (batch_node.op() == "BatchDatasetV2") {
- new_node->add_input(batch_node.input(2));
- } else {
- NodeDef* tmp;
- TF_RETURN_IF_ERROR(
- graph_utils::AddScalarConstNode<bool>(false, output, &tmp));
- new_node->add_input(tmp->name());
- }
-
- // Set `f` and `Targuments` attributes.
- for (auto key : {"f", "Targuments"}) {
- (*new_node->mutable_attr())[key] = map_node->attr().at(key);
- }
- // Set `output_types` and `output_shapes` attributes.
- for (auto key : {"output_shapes", "output_types"}) {
- (*new_node->mutable_attr())[key] = batch_node.attr().at(key);
- }
+ auto* new_node =
+ graph.AddNode(make_map_and_batch_node(*map_node, batch_node, &graph));
+ graph.ReplaceInput(batch_node, *new_node);
// Mark the `Map` and `Batch` nodes for removal.
nodes_to_delete.insert(map_node->name());
nodes_to_delete.insert(batch_node.name());
-
- graph_utils::ReplaceInput(batch_node, *new_node, &graph);
}
- TF_RETURN_IF_ERROR(graph_utils::DeleteNodes(nodes_to_delete, output));
+
+ graph.DeleteNodes(nodes_to_delete);
return Status::OK();
}
diff --git a/tensorflow/core/grappler/optimizers/data/map_and_batch_fusion_test.cc b/tensorflow/core/grappler/optimizers/data/map_and_batch_fusion_test.cc
index 3c1d8d5359..a46c504ac4 100644
--- a/tensorflow/core/grappler/optimizers/data/map_and_batch_fusion_test.cc
+++ b/tensorflow/core/grappler/optimizers/data/map_and_batch_fusion_test.cc
@@ -27,25 +27,21 @@ namespace {
TEST(MapAndBatchFusionTest, FuseMapAndBatchNodesIntoOne) {
GrapplerItem item;
- GraphDef *graph = &item.graph;
- NodeDef *start_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(0, graph, &start_node));
- NodeDef *stop_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(10, graph, &stop_node));
- NodeDef *step_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(1, graph, &step_node));
+ MutableGraphView graph(&item.graph);
+
+ NodeDef *start_node = graph_utils::AddScalarConstNode<int64>(0, &graph);
+ NodeDef *stop_node = graph_utils::AddScalarConstNode<int64>(10, &graph);
+ NodeDef *step_node = graph_utils::AddScalarConstNode<int64>(1, &graph);
std::vector<string> range_inputs(3);
range_inputs[0] = start_node->name();
range_inputs[1] = stop_node->name();
range_inputs[2] = step_node->name();
std::vector<std::pair<string, AttrValue>> range_attrs;
- NodeDef *range_node;
- TF_ASSERT_OK(graph_utils::AddNode("", "RangeDataset", range_inputs,
- range_attrs, graph, &range_node));
- NodeDef *captured_input_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<StringPiece>(
- "hello", graph, &captured_input_node));
+ NodeDef *range_node = graph_utils::AddNode("", "RangeDataset", range_inputs,
+ range_attrs, &graph);
+ NodeDef *captured_input_node =
+ graph_utils::AddScalarConstNode<StringPiece>("hello", &graph);
NodeDef *map_node;
{
@@ -59,13 +55,11 @@ TEST(MapAndBatchFusionTest, FuseMapAndBatchNodesIntoOne) {
AttrValue args_attr;
SetAttrValue("Targuments", &args_attr);
map_attrs[1] = std::make_pair("Targuments", args_attr);
- TF_ASSERT_OK(graph_utils::AddNode("", "MapDataset", map_inputs, map_attrs,
- graph, &map_node));
+ map_node =
+ graph_utils::AddNode("", "MapDataset", map_inputs, map_attrs, &graph);
}
- NodeDef *batch_size_node;
- TF_ASSERT_OK(
- graph_utils::AddScalarConstNode<int64>(5, graph, &batch_size_node));
+ NodeDef *batch_size_node = graph_utils::AddScalarConstNode<int64>(5, &graph);
NodeDef *batch_node;
{
std::vector<string> batch_inputs(2);
@@ -78,16 +72,18 @@ TEST(MapAndBatchFusionTest, FuseMapAndBatchNodesIntoOne) {
AttrValue types_attr;
SetAttrValue("output_types", &types_attr);
batch_attrs[1] = std::make_pair("output_types", types_attr);
- TF_ASSERT_OK(graph_utils::AddNode("", "BatchDataset", batch_inputs,
- batch_attrs, graph, &batch_node));
+ batch_node = graph_utils::AddNode("", "BatchDataset", batch_inputs,
+ batch_attrs, &graph);
}
MapAndBatchFusion optimizer;
GraphDef output;
TF_ASSERT_OK(optimizer.Optimize(nullptr, item, &output));
- EXPECT_FALSE(graph_utils::ContainsNodeWithName(map_node->name(), output));
- EXPECT_FALSE(graph_utils::ContainsNodeWithName(batch_node->name(), output));
+ EXPECT_FALSE(
+ graph_utils::ContainsGraphNodeWithName(map_node->name(), output));
+ EXPECT_FALSE(
+ graph_utils::ContainsGraphNodeWithName(batch_node->name(), output));
EXPECT_TRUE(graph_utils::ContainsNodeWithOp("MapAndBatchDatasetV2", output));
NodeDef map_and_batch_node =
output.node(graph_utils::FindNodeWithOp("MapAndBatchDatasetV2", output));
@@ -96,11 +92,11 @@ TEST(MapAndBatchFusionTest, FuseMapAndBatchNodesIntoOne) {
EXPECT_EQ(map_and_batch_node.input(1), map_node->input(1));
EXPECT_EQ(map_and_batch_node.input(2), batch_node->input(1));
NodeDef num_parallel_calls_node = output.node(
- graph_utils::FindNodeWithName(map_and_batch_node.input(3), output));
+ graph_utils::FindGraphNodeWithName(map_and_batch_node.input(3), output));
EXPECT_EQ(num_parallel_calls_node.attr().at("value").tensor().int64_val(0),
1);
NodeDef drop_remainder_node = output.node(
- graph_utils::FindNodeWithName(map_and_batch_node.input(4), output));
+ graph_utils::FindGraphNodeWithName(map_and_batch_node.input(4), output));
EXPECT_EQ(drop_remainder_node.attr().at("value").tensor().bool_val(0), false);
EXPECT_TRUE(AreAttrValuesEqual(map_and_batch_node.attr().at("f"),
map_node->attr().at("f")));
@@ -114,25 +110,20 @@ TEST(MapAndBatchFusionTest, FuseMapAndBatchNodesIntoOne) {
TEST(MapAndBatchFusionTest, FuseMapAndBatchV2NodesIntoOne) {
GrapplerItem item;
- GraphDef *graph = &item.graph;
- NodeDef *start_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(0, graph, &start_node));
- NodeDef *stop_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(10, graph, &stop_node));
- NodeDef *step_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(1, graph, &step_node));
+ MutableGraphView graph(&item.graph);
+ NodeDef *start_node = graph_utils::AddScalarConstNode<int64>(0, &graph);
+ NodeDef *stop_node = graph_utils::AddScalarConstNode<int64>(10, &graph);
+ NodeDef *step_node = graph_utils::AddScalarConstNode<int64>(1, &graph);
std::vector<string> range_inputs(3);
range_inputs[0] = start_node->name();
range_inputs[1] = stop_node->name();
range_inputs[2] = step_node->name();
std::vector<std::pair<string, AttrValue>> range_attrs;
- NodeDef *range_node;
- TF_ASSERT_OK(graph_utils::AddNode("", "RangeDataset", range_inputs,
- range_attrs, graph, &range_node));
- NodeDef *captured_input_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<StringPiece>(
- "hello", graph, &captured_input_node));
+ NodeDef *range_node = graph_utils::AddNode("", "RangeDataset", range_inputs,
+ range_attrs, &graph);
+ NodeDef *captured_input_node =
+ graph_utils::AddScalarConstNode<StringPiece>("hello", &graph);
NodeDef *map_node;
{
@@ -146,16 +137,13 @@ TEST(MapAndBatchFusionTest, FuseMapAndBatchV2NodesIntoOne) {
AttrValue args_attr;
SetAttrValue("Targuments", &args_attr);
map_attrs[1] = std::make_pair("Targuments", args_attr);
- TF_ASSERT_OK(graph_utils::AddNode("", "MapDataset", map_inputs, map_attrs,
- graph, &map_node));
+ map_node =
+ graph_utils::AddNode("", "MapDataset", map_inputs, map_attrs, &graph);
}
- NodeDef *batch_size_node;
- TF_ASSERT_OK(
- graph_utils::AddScalarConstNode<int64>(5, graph, &batch_size_node));
- NodeDef *drop_remainder_node;
- TF_ASSERT_OK(
- graph_utils::AddScalarConstNode<bool>(true, graph, &drop_remainder_node));
+ NodeDef *batch_size_node = graph_utils::AddScalarConstNode<int64>(5, &graph);
+ NodeDef *drop_remainder_node =
+ graph_utils::AddScalarConstNode<bool>(true, &graph);
NodeDef *batch_node;
{
std::vector<string> batch_inputs(3);
@@ -169,16 +157,18 @@ TEST(MapAndBatchFusionTest, FuseMapAndBatchV2NodesIntoOne) {
AttrValue types_attr;
SetAttrValue("output_types", &types_attr);
batch_attrs[1] = std::make_pair("output_types", types_attr);
- TF_ASSERT_OK(graph_utils::AddNode("", "BatchDatasetV2", batch_inputs,
- batch_attrs, graph, &batch_node));
+ batch_node = graph_utils::AddNode("", "BatchDatasetV2", batch_inputs,
+ batch_attrs, &graph);
}
MapAndBatchFusion optimizer;
GraphDef output;
TF_ASSERT_OK(optimizer.Optimize(nullptr, item, &output));
- EXPECT_FALSE(graph_utils::ContainsNodeWithName(map_node->name(), output));
- EXPECT_FALSE(graph_utils::ContainsNodeWithName(batch_node->name(), output));
+ EXPECT_FALSE(
+ graph_utils::ContainsGraphNodeWithName(map_node->name(), output));
+ EXPECT_FALSE(
+ graph_utils::ContainsGraphNodeWithName(batch_node->name(), output));
EXPECT_TRUE(graph_utils::ContainsNodeWithOp("MapAndBatchDatasetV2", output));
NodeDef map_and_batch_node =
output.node(graph_utils::FindNodeWithOp("MapAndBatchDatasetV2", output));
@@ -187,7 +177,7 @@ TEST(MapAndBatchFusionTest, FuseMapAndBatchV2NodesIntoOne) {
EXPECT_EQ(map_and_batch_node.input(1), map_node->input(1));
EXPECT_EQ(map_and_batch_node.input(2), batch_node->input(1));
NodeDef num_parallel_calls_node = output.node(
- graph_utils::FindNodeWithName(map_and_batch_node.input(3), output));
+ graph_utils::FindGraphNodeWithName(map_and_batch_node.input(3), output));
EXPECT_EQ(num_parallel_calls_node.attr().at("value").tensor().int64_val(0),
1);
EXPECT_EQ(map_and_batch_node.input(4), batch_node->input(2));
@@ -203,28 +193,22 @@ TEST(MapAndBatchFusionTest, FuseMapAndBatchV2NodesIntoOne) {
TEST(MapAndBatchFusionTest, FuseParallelMapAndBatchNodesIntoOne) {
GrapplerItem item;
- GraphDef *graph = &item.graph;
- NodeDef *start_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(0, graph, &start_node));
- NodeDef *stop_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(10, graph, &stop_node));
- NodeDef *step_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(1, graph, &step_node));
+ MutableGraphView graph(&item.graph);
+ NodeDef *start_node = graph_utils::AddScalarConstNode<int64>(0, &graph);
+ NodeDef *stop_node = graph_utils::AddScalarConstNode<int64>(10, &graph);
+ NodeDef *step_node = graph_utils::AddScalarConstNode<int64>(1, &graph);
std::vector<string> range_inputs(3);
range_inputs[0] = start_node->name();
range_inputs[1] = stop_node->name();
range_inputs[2] = step_node->name();
std::vector<std::pair<string, AttrValue>> range_attrs;
- NodeDef *range_node;
- TF_ASSERT_OK(graph_utils::AddNode("", "RangeDataset", range_inputs,
- range_attrs, graph, &range_node));
- NodeDef *captured_input_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<StringPiece>(
- "hello", graph, &captured_input_node));
- NodeDef *num_parallel_calls_node;
- TF_ASSERT_OK(
- graph_utils::AddScalarConstNode<int>(2, graph, &num_parallel_calls_node));
+ NodeDef *range_node = graph_utils::AddNode("", "RangeDataset", range_inputs,
+ range_attrs, &graph);
+ NodeDef *captured_input_node =
+ graph_utils::AddScalarConstNode<StringPiece>("hello", &graph);
+ NodeDef *num_parallel_calls_node =
+ graph_utils::AddScalarConstNode<int>(2, &graph);
NodeDef *map_node;
{
@@ -239,13 +223,11 @@ TEST(MapAndBatchFusionTest, FuseParallelMapAndBatchNodesIntoOne) {
AttrValue args_attr;
SetAttrValue("Targuments", &args_attr);
map_attrs[1] = std::make_pair("Targuments", args_attr);
- TF_ASSERT_OK(graph_utils::AddNode("", "ParallelMapDataset", map_inputs,
- map_attrs, graph, &map_node));
+ map_node = graph_utils::AddNode("", "ParallelMapDataset", map_inputs,
+ map_attrs, &graph);
}
- NodeDef *batch_size_node;
- TF_ASSERT_OK(
- graph_utils::AddScalarConstNode<int64>(5, graph, &batch_size_node));
+ NodeDef *batch_size_node = graph_utils::AddScalarConstNode<int64>(5, &graph);
NodeDef *batch_node;
{
std::vector<string> batch_inputs(2);
@@ -258,16 +240,18 @@ TEST(MapAndBatchFusionTest, FuseParallelMapAndBatchNodesIntoOne) {
AttrValue types_attr;
SetAttrValue("output_types", &types_attr);
batch_attrs[1] = std::make_pair("output_types", types_attr);
- TF_ASSERT_OK(graph_utils::AddNode("", "BatchDataset", batch_inputs,
- batch_attrs, graph, &batch_node));
+ batch_node = graph_utils::AddNode("", "BatchDataset", batch_inputs,
+ batch_attrs, &graph);
}
MapAndBatchFusion optimizer;
GraphDef output;
TF_ASSERT_OK(optimizer.Optimize(nullptr, item, &output));
- EXPECT_FALSE(graph_utils::ContainsNodeWithName(map_node->name(), output));
- EXPECT_FALSE(graph_utils::ContainsNodeWithName(batch_node->name(), output));
+ EXPECT_FALSE(
+ graph_utils::ContainsGraphNodeWithName(map_node->name(), output));
+ EXPECT_FALSE(
+ graph_utils::ContainsGraphNodeWithName(batch_node->name(), output));
EXPECT_TRUE(graph_utils::ContainsNodeWithOp("MapAndBatchDatasetV2", output));
NodeDef map_and_batch_node =
output.node(graph_utils::FindNodeWithOp("MapAndBatchDatasetV2", output));
@@ -276,11 +260,11 @@ TEST(MapAndBatchFusionTest, FuseParallelMapAndBatchNodesIntoOne) {
EXPECT_EQ(map_and_batch_node.input(1), map_node->input(1));
EXPECT_EQ(map_and_batch_node.input(2), batch_node->input(1));
NodeDef num_parallel_calls_node2 = output.node(
- graph_utils::FindNodeWithName(map_and_batch_node.input(3), output));
+ graph_utils::FindGraphNodeWithName(map_and_batch_node.input(3), output));
EXPECT_EQ(num_parallel_calls_node2.attr().at("value").tensor().int64_val(0),
2);
NodeDef drop_remainder_node = output.node(
- graph_utils::FindNodeWithName(map_and_batch_node.input(4), output));
+ graph_utils::FindGraphNodeWithName(map_and_batch_node.input(4), output));
EXPECT_EQ(drop_remainder_node.attr().at("value").tensor().bool_val(0), false);
EXPECT_TRUE(AreAttrValuesEqual(map_and_batch_node.attr().at("f"),
map_node->attr().at("f")));
@@ -294,27 +278,21 @@ TEST(MapAndBatchFusionTest, FuseParallelMapAndBatchNodesIntoOne) {
TEST(MapAndBatchFusionTest, NoChange) {
GrapplerItem item;
- GraphDef *graph = &item.graph;
+ MutableGraphView graph(&item.graph);
- NodeDef *start_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(0, graph, &start_node));
- NodeDef *stop_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(10, graph, &stop_node));
- NodeDef *step_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(1, graph, &step_node));
+ NodeDef *start_node = graph_utils::AddScalarConstNode<int64>(0, &graph);
+ NodeDef *stop_node = graph_utils::AddScalarConstNode<int64>(10, &graph);
+ NodeDef *step_node = graph_utils::AddScalarConstNode<int64>(1, &graph);
std::vector<string> range_inputs(3);
range_inputs[0] = start_node->name();
range_inputs[1] = stop_node->name();
range_inputs[2] = step_node->name();
std::vector<std::pair<string, AttrValue>> range_attrs;
- NodeDef *range_node;
- TF_ASSERT_OK(graph_utils::AddNode("", "RangeDataset", range_inputs,
- range_attrs, graph, &range_node));
+ NodeDef *range_node = graph_utils::AddNode("", "RangeDataset", range_inputs,
+ range_attrs, &graph);
- NodeDef *batch_size_node;
- TF_ASSERT_OK(
- graph_utils::AddScalarConstNode<int64>(5, graph, &batch_size_node));
+ NodeDef *batch_size_node = graph_utils::AddScalarConstNode<int64>(5, &graph);
std::vector<string> batch_inputs(2);
batch_inputs[0] = range_node->name();
batch_inputs[1] = batch_size_node->name();
@@ -325,15 +303,13 @@ TEST(MapAndBatchFusionTest, NoChange) {
AttrValue types_attr;
SetAttrValue("output_types", &types_attr);
batch_attrs[1] = std::make_pair("output_types", types_attr);
- NodeDef *batch_node;
- TF_ASSERT_OK(graph_utils::AddNode("", "BatchDataset", batch_inputs,
- batch_attrs, graph, &batch_node));
+ graph_utils::AddNode("", "BatchDataset", batch_inputs, batch_attrs, &graph);
MapAndBatchFusion optimizer;
GraphDef output;
TF_ASSERT_OK(optimizer.Optimize(nullptr, item, &output));
- EXPECT_TRUE(graph_utils::Compare(*graph, output));
+ EXPECT_TRUE(graph_utils::Compare(*graph.GetGraph(), output));
}
} // namespace
diff --git a/tensorflow/core/grappler/optimizers/data/map_fusion.cc b/tensorflow/core/grappler/optimizers/data/map_fusion.cc
new file mode 100644
index 0000000000..707f4a3407
--- /dev/null
+++ b/tensorflow/core/grappler/optimizers/data/map_fusion.cc
@@ -0,0 +1,261 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/core/grappler/optimizers/data/map_fusion.h"
+
+#include "tensorflow/core/framework/attr_value.pb.h"
+#include "tensorflow/core/framework/node_def.pb.h"
+#include "tensorflow/core/grappler/clusters/cluster.h"
+#include "tensorflow/core/grappler/grappler_item.h"
+#include "tensorflow/core/grappler/mutable_graph_view.h"
+#include "tensorflow/core/grappler/op_types.h"
+#include "tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.h"
+#include "tensorflow/core/grappler/optimizers/data/graph_utils.h"
+#include "tensorflow/core/grappler/utils.h"
+#include "tensorflow/core/grappler/utils/topological_sort.h"
+#include "tensorflow/core/platform/protobuf.h"
+
+namespace tensorflow {
+namespace grappler {
+namespace {
+
+// Sets basic function parameters and copies attributes from parent and map
+// node.
+NodeDef MakeFusedNode(const NodeDef& parent_map_node, const NodeDef& map_node,
+ const FunctionDef& fused_function,
+ MutableGraphView* graph) {
+ NodeDef fused_node;
+ graph_utils::SetUniqueGraphNodeName("fused_map", graph->GetGraph(),
+ &fused_node);
+
+ fused_node.set_op("MapDataset");
+ fused_node.add_input(parent_map_node.input(0));
+
+ auto copy_attribute = [](const string& attribute_name, const NodeDef& from,
+ NodeDef* to) {
+ (*to->mutable_attr())[attribute_name] = from.attr().at(attribute_name);
+ };
+
+ auto attr = parent_map_node.attr().at("f");
+ *attr.mutable_func()->mutable_name() = fused_function.signature().name();
+ (*fused_node.mutable_attr())["f"] = std::move(attr);
+
+ copy_attribute("Targuments", parent_map_node, &fused_node);
+
+ for (auto key : {"output_shapes", "output_types"})
+ copy_attribute(key, map_node, &fused_node);
+
+ return fused_node;
+}
+
+string ParseNodeConnection(const string& name) {
+ // If input/output node name has semicolon, take the prefix. Otherwise take
+ // the whole string.
+ return name.substr(0, name.find(':'));
+}
+
+string ParseOutputNode(const string& name) {
+ return name.substr(name.find(':'), string::npos);
+}
+
+const string& GetOutputNode(const FunctionDef& parent_function,
+ int output_idx) {
+ const auto& ret_output_name =
+ parent_function.signature().output_arg(output_idx).name();
+ return parent_function.ret().at(ret_output_name);
+}
+
+// Nodes that will be added to the function can have the same name as the nodes
+// from parent function. We need to rename them and the connections of the
+// inputs that refer to them.
+void RenameFunctionNodes(FunctionDef* fused_function,
+ protobuf::RepeatedPtrField<NodeDef>* nodes_to_fuse) {
+ std::unordered_map<string, string> changed_node_names;
+ for (NodeDef& function_node : *nodes_to_fuse) {
+ string name_before = function_node.name();
+ graph_utils::SetUniqueFunctionNodeName(name_before, fused_function,
+ &function_node);
+ if (name_before != function_node.name())
+ changed_node_names[name_before] = function_node.name();
+ }
+
+ auto update_name = [&changed_node_names](string* input) {
+ string input_node = ParseNodeConnection(*input);
+ if (changed_node_names.count(input_node) == 0) return;
+ const string& new_node_name = changed_node_names.at(input_node);
+ *input = new_node_name + ParseOutputNode(*input);
+ };
+
+ for (NodeDef& function_node : *nodes_to_fuse) {
+ for (string& input : *function_node.mutable_input()) {
+ update_name(&input);
+ }
+ }
+
+ for (auto& ret : *fused_function->mutable_ret()) update_name(&ret.second);
+}
+
+// This function adds new nodes and changes their input to the output nodes
+// of parent function.
+void FuseFunctionNodes(const FunctionDef& parent_function,
+ const FunctionDef& function,
+ protobuf::RepeatedPtrField<NodeDef>* nodes_to_fuse) {
+ const auto number_of_outputs = parent_function.signature().output_arg_size();
+ CHECK(number_of_outputs == function.signature().input_arg_size())
+ << "The number of input arguments of function "
+ << function.signature().name()
+ << " should be the same as the number of output arguments of function "
+ << parent_function.signature().name() << ".";
+
+ for (int output_idx = 0; output_idx < number_of_outputs; output_idx++) {
+ const string& output = GetOutputNode(parent_function, output_idx);
+
+ const auto& input_node_name =
+ function.signature().input_arg(output_idx).name();
+
+ for (NodeDef& function_node : *nodes_to_fuse) {
+ for (auto& node_input : *function_node.mutable_input()) {
+ auto parsed_name = ParseNodeConnection(node_input);
+ if (parsed_name != input_node_name) continue;
+
+ node_input = output;
+ }
+ }
+ }
+}
+
+// This function looks for direct edges from input to return and rewrites
+// them to the coresponding input of the return of parent_function.
+void FuseReturns(const FunctionDef& parent_function,
+ const FunctionDef& function, FunctionDef* fused_function) {
+ const auto number_of_inputs = function.signature().input_arg_size();
+
+ for (auto& ret : *fused_function->mutable_ret()) {
+ auto return_input = ParseNodeConnection(ret.second);
+ for (int input_idx = 0; input_idx < number_of_inputs; input_idx++) {
+ const auto& input_arg = function.signature().input_arg(input_idx);
+ if (return_input != input_arg.name()) continue;
+
+ ret.second = GetOutputNode(parent_function, input_idx);
+ }
+ }
+}
+
+// This function produces new function that is a result of fusion of
+// `parent_function` with `function`.
+FunctionDef* FuseFunctions(const FunctionDef& parent_function,
+ const FunctionDef& function,
+ FunctionDefLibrary* library) {
+ FunctionDef* fused_function = library->add_function();
+ graph_utils::SetUniqueGraphFunctionName("fused_function", library,
+ fused_function);
+
+ // Copy input signature from parent function.
+ *fused_function->mutable_signature()->mutable_input_arg() =
+ parent_function.signature().input_arg();
+
+ fused_function->mutable_node_def()->CopyFrom(parent_function.node_def());
+ // This code assumes functions does not have any attributes. If this is
+ // not the case, we need to merge attributes and fix name conflicts.
+ CHECK(parent_function.attr_size() == 0 && function.attr_size() == 0 &&
+ "Functions with attributes are currently not supported");
+
+ // Copy the returns and output signature from the second node.
+ auto nodes_to_fuse = function.node_def();
+ fused_function->mutable_signature()->mutable_output_arg()->CopyFrom(
+ function.signature().output_arg());
+ *fused_function->mutable_ret() = function.ret();
+
+ RenameFunctionNodes(fused_function, &nodes_to_fuse);
+ FuseFunctionNodes(parent_function, function, &nodes_to_fuse);
+ FuseReturns(parent_function, function, fused_function);
+
+ // Copy transformed nodes from the second function.
+ fused_function->mutable_node_def()->MergeFrom(nodes_to_fuse);
+
+ return fused_function;
+}
+
+} // namespace
+
+Status MapFusion::Optimize(Cluster* cluster, const GrapplerItem& item,
+ GraphDef* output) {
+ GraphDef sorted_old_graph = item.graph;
+ TF_RETURN_IF_ERROR(TopologicalSort(&sorted_old_graph));
+ *output = sorted_old_graph;
+
+ MutableGraphView graph(output);
+ std::set<string> nodes_to_delete;
+ FunctionLibraryDefinition function_library(OpRegistry::Global(),
+ item.graph.library());
+
+ auto get_map_node = [](const NodeDef& node) -> const NodeDef* {
+ // TODO(prazek): we could also handle ParallelMapDataset and
+ // MapAndBatchDataset.
+ if (node.op() == "MapDataset") return &node;
+ return nullptr;
+ };
+
+ auto get_fused_function = [&function_library, &output](
+ const NodeDef* parent_map_node,
+ const NodeDef* map_node) {
+ const auto& parent_fun = parent_map_node->attr().at("f");
+ const FunctionDef* parent_func =
+ function_library.Find(parent_fun.func().name());
+ const auto& fun = map_node->attr().at("f");
+ const FunctionDef* func = function_library.Find(fun.func().name());
+
+ return FuseFunctions(*parent_func, *func, output->mutable_library());
+ };
+
+ for (const NodeDef& node : sorted_old_graph.node()) {
+ const NodeDef* map_node = get_map_node(node);
+ if (!map_node) continue;
+
+ GraphView::InputPort input_port = graph.GetInputPort(map_node->name(), 0);
+ const NodeDef* parent_map_node =
+ get_map_node(*graph.GetRegularFanin(input_port).node);
+ if (!parent_map_node) continue;
+
+ const auto* fused_function = get_fused_function(parent_map_node, map_node);
+ const auto* fused_maps_node = graph.AddNode(
+ MakeFusedNode(*parent_map_node, *map_node, *fused_function, &graph));
+
+ graph.ReplaceInput(*map_node, *fused_maps_node);
+
+ // TODO(prazek): we should run some optimizations on the fused map
+ // functions, or make sure that optimization passes run after map
+ // fusion.
+ TF_RETURN_IF_ERROR(function_library.AddFunctionDef(*fused_function));
+
+ // TODO(prazek): we could also remove map functions from library if they
+ // are not used anymore.
+ nodes_to_delete.insert(parent_map_node->name());
+ nodes_to_delete.insert(map_node->name());
+ }
+
+ graph.DeleteNodes(nodes_to_delete);
+ return Status::OK();
+}
+
+void MapFusion::Feedback(Cluster* cluster, const GrapplerItem& item,
+ const GraphDef& optimize_output, double result) {
+ // no-op
+}
+
+REGISTER_GRAPH_OPTIMIZER_AS(MapFusion, "map_fusion");
+
+} // end namespace grappler
+} // end namespace tensorflow
diff --git a/tensorflow/core/grappler/optimizers/data/map_fusion.h b/tensorflow/core/grappler/optimizers/data/map_fusion.h
new file mode 100644
index 0000000000..a6a06592b8
--- /dev/null
+++ b/tensorflow/core/grappler/optimizers/data/map_fusion.h
@@ -0,0 +1,47 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#ifndef TENSORFLOW_CORE_GRAPPLER_OPTIMIZERS_DATA_MAP_FUSION_H_
+#define TENSORFLOW_CORE_GRAPPLER_OPTIMIZERS_DATA_MAP_FUSION_H_
+
+#include "tensorflow/core/grappler/optimizers/custom_graph_optimizer.h"
+
+namespace tensorflow {
+namespace grappler {
+
+// This optimization fuses map transformations by merging their map functions.
+class MapFusion : public CustomGraphOptimizer {
+ public:
+ MapFusion() = default;
+ ~MapFusion() override = default;
+
+ string name() const override { return "map_fusion"; };
+
+ Status Init(
+ const tensorflow::RewriterConfig_CustomGraphOptimizer* config) override {
+ return Status::OK();
+ }
+
+ Status Optimize(Cluster* cluster, const GrapplerItem& item,
+ GraphDef* output) override;
+
+ void Feedback(Cluster* cluster, const GrapplerItem& item,
+ const GraphDef& optimize_output, double result) override;
+};
+
+} // end namespace grappler
+} // end namespace tensorflow
+
+#endif // TENSORFLOW_CORE_GRAPPLER_OPTIMIZERS_DATA_MAP_FUSION_H_
diff --git a/tensorflow/core/grappler/optimizers/data/map_fusion_test.cc b/tensorflow/core/grappler/optimizers/data/map_fusion_test.cc
new file mode 100644
index 0000000000..df6c19dc7c
--- /dev/null
+++ b/tensorflow/core/grappler/optimizers/data/map_fusion_test.cc
@@ -0,0 +1,90 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/core/grappler/optimizers/data/map_fusion.h"
+
+#include "tensorflow/core/framework/attr_value_util.h"
+#include "tensorflow/core/framework/function_testlib.h"
+#include "tensorflow/core/framework/tensor_testutil.h"
+#include "tensorflow/core/grappler/grappler_item.h"
+#include "tensorflow/core/grappler/optimizers/data/graph_utils.h"
+
+#include "tensorflow/core/lib/core/status_test_util.h"
+#include "tensorflow/core/platform/test.h"
+
+namespace tensorflow {
+namespace grappler {
+namespace {
+
+NodeDef MakeMapNode(StringPiece name, StringPiece input_node_name) {
+ return test::function::NDef(
+ name, "MapDataset", {input_node_name.ToString()},
+ {{"f", FunctionDefHelper::FunctionRef("XTimesTwo")},
+ {"Targuments", {}},
+ {"output_shapes", {}},
+ {"output_types", {}}});
+}
+
+TEST(MapFusionTest, FuseTwoMapNodesIntoOne) {
+ using test::function::NDef;
+ GrapplerItem item;
+ item.graph = test::function::GDef(
+ {NDef("start", "Const", {}, {{"value", 0}, {"dtype", DT_INT32}}),
+ NDef("stop", "Const", {}, {{"value", 10}, {"dtype", DT_INT32}}),
+ NDef("step", "Const", {}, {{"value", 1}, {"dtype", DT_INT32}}),
+ NDef("range", "RangeDataset", {"start", "stop", "step"}, {}),
+ MakeMapNode("map1", "range"), MakeMapNode("map2", "map1")},
+ // FunctionLib
+ {
+ test::function::XTimesTwo(),
+ });
+
+ MapFusion optimizer;
+ GraphDef output;
+ TF_ASSERT_OK(optimizer.Optimize(nullptr, item, &output));
+ EXPECT_TRUE(graph_utils::ContainsNodeWithOp("MapDataset", output));
+ EXPECT_FALSE(graph_utils::ContainsGraphNodeWithName("map1", output));
+ EXPECT_FALSE(graph_utils::ContainsGraphNodeWithName("map2", output));
+}
+
+TEST(MapFusionTest, FuseThreeNodesIntoOne) {
+ using test::function::NDef;
+ GrapplerItem item;
+ item.graph = test::function::GDef(
+ {NDef("start", "Const", {}, {{"value", 0}, {"dtype", DT_INT32}}),
+ NDef("stop", "Const", {}, {{"value", 10}, {"dtype", DT_INT32}}),
+ NDef("step", "Const", {}, {{"value", 1}, {"dtype", DT_INT32}}),
+ NDef("filename", "Const", {}, {{"value", ""}, {"dtype", DT_STRING}}),
+ NDef("range", "RangeDataset", {"start", "stop", "step"}, {}),
+ MakeMapNode("map1", "range"), MakeMapNode("map2", "map1"),
+ MakeMapNode("map3", "map2"),
+ NDef("cache", "CacheDataset", {"map3", "filename"}, {})},
+ // FunctionLib
+ {
+ test::function::XTimesTwo(),
+ });
+
+ MapFusion optimizer;
+ GraphDef output;
+ TF_ASSERT_OK(optimizer.Optimize(nullptr, item, &output));
+ EXPECT_TRUE(graph_utils::ContainsNodeWithOp("MapDataset", output));
+ EXPECT_FALSE(graph_utils::ContainsGraphNodeWithName("map1", output));
+ EXPECT_FALSE(graph_utils::ContainsGraphNodeWithName("map2", output));
+ EXPECT_FALSE(graph_utils::ContainsGraphNodeWithName("map3", output));
+}
+
+} // namespace
+} // namespace grappler
+} // namespace tensorflow
diff --git a/tensorflow/core/grappler/optimizers/data/noop_elimination.cc b/tensorflow/core/grappler/optimizers/data/noop_elimination.cc
index 5670966367..55d57b3b97 100644
--- a/tensorflow/core/grappler/optimizers/data/noop_elimination.cc
+++ b/tensorflow/core/grappler/optimizers/data/noop_elimination.cc
@@ -18,8 +18,8 @@ limitations under the License.
#include "tensorflow/core/framework/attr_value.pb.h"
#include "tensorflow/core/framework/node_def.pb.h"
#include "tensorflow/core/grappler/clusters/cluster.h"
-#include "tensorflow/core/grappler/graph_view.h"
#include "tensorflow/core/grappler/grappler_item.h"
+#include "tensorflow/core/grappler/mutable_graph_view.h"
#include "tensorflow/core/grappler/op_types.h"
#include "tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.h"
#include "tensorflow/core/grappler/optimizers/data/graph_utils.h"
@@ -64,18 +64,19 @@ bool IsNoOp(const NodeDef& node, const GraphView& graph) {
Status NoOpElimination::Optimize(Cluster* cluster, const GrapplerItem& item,
GraphDef* output) {
*output = item.graph;
- GraphView graph(output);
+ MutableGraphView graph(output);
std::set<string> nodes_to_delete;
for (const NodeDef& node : item.graph.node()) {
if (!IsNoOp(node, graph)) continue;
GraphView::InputPort input_port = graph.GetInputPort(node.name(), 0);
NodeDef* const parent = graph.GetRegularFanin(input_port).node;
- graph_utils::ReplaceInput(node, *parent, &graph);
+ graph.ReplaceInput(node, *parent);
nodes_to_delete.insert(node.name());
}
- TF_RETURN_IF_ERROR(graph_utils::DeleteNodes(nodes_to_delete, output));
+
+ graph.DeleteNodes(nodes_to_delete);
return Status::OK();
}
diff --git a/tensorflow/core/grappler/optimizers/data/noop_elimination_test.cc b/tensorflow/core/grappler/optimizers/data/noop_elimination_test.cc
index 8628b16ea5..a6cc63edba 100644
--- a/tensorflow/core/grappler/optimizers/data/noop_elimination_test.cc
+++ b/tensorflow/core/grappler/optimizers/data/noop_elimination_test.cc
@@ -35,36 +35,32 @@ std::vector<std::pair<string, AttrValue>> GetCommonAttributes() {
return commonAttributes;
}
-void MakeUnaryNode(GraphDef *graph, const std::string &node_type, int count,
- string input_node, NodeDef **return_node) {
- NodeDef *node_count;
- TF_ASSERT_OK(
- graph_utils::AddScalarConstNode<int64>(count, graph, &node_count));
- TF_ASSERT_OK(graph_utils::AddNode("", node_type,
- {std::move(input_node), node_count->name()},
- GetCommonAttributes(), graph, return_node));
+NodeDef *MakeUnaryNode(const std::string &node_type, int count,
+ string input_node, MutableGraphView *graph) {
+ NodeDef *node_count = graph_utils::AddScalarConstNode<int64>(count, graph);
+ return graph_utils::AddNode("", node_type,
+ {std::move(input_node), node_count->name()},
+ GetCommonAttributes(), graph);
}
-void MakeCacheNode(GraphDef *graph, string input_node, NodeDef **return_node) {
- NodeDef *node_filename;
- TF_ASSERT_OK(
- graph_utils::AddScalarConstNode<StringPiece>("", graph, &node_filename));
- TF_ASSERT_OK(graph_utils::AddNode(
- "", "CacheDataset", {std::move(input_node), node_filename->name()},
- GetCommonAttributes(), graph, return_node));
+NodeDef *MakeCacheNode(string input_node, MutableGraphView *graph) {
+ NodeDef *node_filename =
+ graph_utils::AddScalarConstNode<StringPiece>("", graph);
+ return graph_utils::AddNode("", "CacheDataset",
+ {std::move(input_node), node_filename->name()},
+ GetCommonAttributes(), graph);
}
-void MakeRangeNode(GraphDef *graph, NodeDef **range_node) {
- NodeDef *start_node, *stop_node, *step_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(0, graph, &start_node));
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(10, graph, &stop_node));
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(1, graph, &step_node));
+NodeDef *MakeRangeNode(MutableGraphView *graph) {
+ auto *start_node = graph_utils::AddScalarConstNode<int64>(0, graph);
+ auto *stop_node = graph_utils::AddScalarConstNode<int64>(10, graph);
+ auto *step_node = graph_utils::AddScalarConstNode<int64>(1, graph);
std::vector<string> range_inputs = {start_node->name(), stop_node->name(),
step_node->name()};
- TF_ASSERT_OK(graph_utils::AddNode("", "RangeDataset", range_inputs,
- GetCommonAttributes(), graph, range_node));
+ return graph_utils::AddNode("", "RangeDataset", range_inputs,
+ GetCommonAttributes(), graph);
}
struct NoOpLastEliminationTest
@@ -74,23 +70,22 @@ struct NoOpLastEliminationTest
// transformations at the end of the pipeline.
TEST_P(NoOpLastEliminationTest, EliminateLastNoOpNode) {
GrapplerItem item;
- GraphDef *graph = &item.graph;
+ MutableGraphView graph(&item.graph);
const std::string &node_type = std::get<0>(GetParam());
const int node_count = std::get<1>(GetParam());
const bool should_keep_node = std::get<2>(GetParam());
- NodeDef *range_node;
- MakeRangeNode(graph, &range_node);
+ NodeDef *range_node = MakeRangeNode(&graph);
- NodeDef *node;
- MakeUnaryNode(graph, node_type, node_count, range_node->name(), &node);
+ NodeDef *node =
+ MakeUnaryNode(node_type, node_count, range_node->name(), &graph);
NoOpElimination optimizer;
GraphDef output;
TF_ASSERT_OK(optimizer.Optimize(nullptr, item, &output));
- EXPECT_EQ(graph_utils::ContainsNodeWithName(node->name(), output),
+ EXPECT_EQ(graph_utils::ContainsGraphNodeWithName(node->name(), output),
should_keep_node);
}
@@ -113,30 +108,29 @@ struct NoOpMiddleEliminationTest
// transformations int the middle of the pipeline.
TEST_P(NoOpMiddleEliminationTest, EliminateMiddleNoOpNode) {
GrapplerItem item;
- GraphDef *graph = &item.graph;
+ MutableGraphView graph(&item.graph);
const std::string &node_type = std::get<0>(GetParam());
const int node_count = std::get<1>(GetParam());
const bool should_keep_node = std::get<2>(GetParam());
- NodeDef *range_node;
- MakeRangeNode(graph, &range_node);
+ NodeDef *range_node = MakeRangeNode(&graph);
- NodeDef *node;
- MakeUnaryNode(graph, node_type, node_count, range_node->name(), &node);
+ NodeDef *node =
+ MakeUnaryNode(node_type, node_count, range_node->name(), &graph);
- NodeDef *cache_node;
- MakeCacheNode(graph, node->name(), &cache_node);
+ NodeDef *cache_node = MakeCacheNode(node->name(), &graph);
NoOpElimination optimizer;
GraphDef output;
TF_ASSERT_OK(optimizer.Optimize(nullptr, item, &output));
- EXPECT_EQ(graph_utils::ContainsNodeWithName(node->name(), output),
+ EXPECT_EQ(graph_utils::ContainsGraphNodeWithName(node->name(), output),
should_keep_node);
- EXPECT_TRUE(graph_utils::ContainsNodeWithName(cache_node->name(), output));
+ EXPECT_TRUE(
+ graph_utils::ContainsGraphNodeWithName(cache_node->name(), output));
- NodeDef cache_node_out =
- output.node(graph_utils::FindNodeWithName(cache_node->name(), output));
+ NodeDef cache_node_out = output.node(
+ graph_utils::FindGraphNodeWithName(cache_node->name(), output));
EXPECT_EQ(cache_node_out.input_size(), 2);
auto last_node_input = (should_keep_node ? node : range_node)->name();
@@ -162,41 +156,40 @@ struct NoOpMultipleEliminationTest : ::testing::TestWithParam<NodesTypes> {};
// multiple noop nodes.
TEST_P(NoOpMultipleEliminationTest, EliminateMultipleNoOpNode) {
GrapplerItem item;
- GraphDef *graph = &item.graph;
+ MutableGraphView graph(&item.graph);
static_assert(std::tuple_size<NodesTypes>::value == 2,
"Make sure to include everything in the test");
const std::vector<std::pair<string, int>> noop_nodes = {
std::get<0>(GetParam()), std::get<1>(GetParam())};
- NodeDef *range_node;
- MakeRangeNode(graph, &range_node);
+ NodeDef *range_node = MakeRangeNode(&graph);
NodeDef *previous = range_node;
std::vector<string> nodes_to_remove;
nodes_to_remove.reserve(noop_nodes.size());
for (const auto &noop_node : noop_nodes) {
- NodeDef *node;
- MakeUnaryNode(graph, noop_node.first, noop_node.second, previous->name(),
- &node);
+ NodeDef *node = MakeUnaryNode(noop_node.first, noop_node.second,
+ previous->name(), &graph);
nodes_to_remove.push_back(node->name());
previous = node;
}
- NodeDef *cache_node;
- MakeCacheNode(graph, previous->name(), &cache_node);
+ NodeDef *cache_node = MakeCacheNode(previous->name(), &graph);
NoOpElimination optimizer;
GraphDef output;
TF_ASSERT_OK(optimizer.Optimize(nullptr, item, &output));
for (const auto &noop_node_name : nodes_to_remove)
- EXPECT_FALSE(graph_utils::ContainsNodeWithName(noop_node_name, output));
+ EXPECT_FALSE(
+ graph_utils::ContainsGraphNodeWithName(noop_node_name, output));
- EXPECT_TRUE(graph_utils::ContainsNodeWithName(cache_node->name(), output));
+ EXPECT_TRUE(
+ graph_utils::ContainsGraphNodeWithName(cache_node->name(), output));
- NodeDef cache_node_out =
- output.node(graph_utils::FindNodeWithName(cache_node->name(), output));
+ NodeDef cache_node_out = output.node(
+ graph_utils::FindGraphNodeWithName(cache_node->name(), output));
EXPECT_EQ(cache_node_out.input_size(), 2);
EXPECT_EQ(cache_node_out.input(0), range_node->name());
diff --git a/tensorflow/core/grappler/optimizers/data/shuffle_and_repeat_fusion.cc b/tensorflow/core/grappler/optimizers/data/shuffle_and_repeat_fusion.cc
index 8332fb0b1e..7c7161c5b2 100644
--- a/tensorflow/core/grappler/optimizers/data/shuffle_and_repeat_fusion.cc
+++ b/tensorflow/core/grappler/optimizers/data/shuffle_and_repeat_fusion.cc
@@ -18,8 +18,8 @@ limitations under the License.
#include "tensorflow/core/framework/attr_value.pb.h"
#include "tensorflow/core/framework/node_def.pb.h"
#include "tensorflow/core/grappler/clusters/cluster.h"
-#include "tensorflow/core/grappler/graph_view.h"
#include "tensorflow/core/grappler/grappler_item.h"
+#include "tensorflow/core/grappler/mutable_graph_view.h"
#include "tensorflow/core/grappler/op_types.h"
#include "tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.h"
#include "tensorflow/core/grappler/optimizers/data/graph_utils.h"
@@ -38,55 +38,62 @@ Status ShuffleAndRepeatFusion::Optimize(Cluster* cluster,
const GrapplerItem& item,
GraphDef* output) {
*output = item.graph;
- GraphView graph(output);
+ MutableGraphView graph(output);
std::set<string> nodes_to_delete;
- for (const NodeDef& node : item.graph.node()) {
- if (node.op() != "RepeatDataset") {
- continue;
- }
- // Use a more descriptive variable name now that we know the node type.
- const NodeDef repeat_node(node);
- GraphView::InputPort input_port = graph.GetInputPort(repeat_node.name(), 0);
- NodeDef* node2 = graph.GetRegularFanin(input_port).node;
- if (node2->op() != "ShuffleDataset") {
- continue;
- }
-
- NodeDef* new_node = output->add_node();
- new_node->set_op(kFusedOpName);
- graph_utils::SetUniqueName(kFusedOpName, output, new_node);
-
- // Use a more descriptive variable name now that we know the node type.
- NodeDef* shuffle_node = node2;
+ auto make_shuffle_and_repeat_node = [&output](const NodeDef& shuffle_node,
+ const NodeDef& repeat_node) {
+ NodeDef new_node;
+ new_node.set_op(kFusedOpName);
+ graph_utils::SetUniqueGraphNodeName(kFusedOpName, output, &new_node);
// Set the `input` input argument.
- new_node->add_input(shuffle_node->input(0));
+ new_node.add_input(shuffle_node.input(0));
// Set the `buffer_size` input argument.
- new_node->add_input(shuffle_node->input(1));
+ new_node.add_input(shuffle_node.input(1));
// Set the `seed` input argument.
- new_node->add_input(shuffle_node->input(2));
+ new_node.add_input(shuffle_node.input(2));
// Set the `seed2` input argument.
- new_node->add_input(shuffle_node->input(3));
+ new_node.add_input(shuffle_node.input(3));
// Set the `count` input argument.
- new_node->add_input(repeat_node.input(1));
+ new_node.add_input(repeat_node.input(1));
// Set `output_types` and `output_shapes` attributes.
for (auto key : {"output_shapes", "output_types"}) {
- (*new_node->mutable_attr())[key] = repeat_node.attr().at(key);
+ (*new_node.mutable_attr())[key] = repeat_node.attr().at(key);
}
+ return new_node;
+ };
+
+ for (const NodeDef& node : item.graph.node()) {
+ if (node.op() != "RepeatDataset") {
+ continue;
+ }
+
+ // Use a more descriptive variable name now that we know the node type.
+ const NodeDef& repeat_node = node;
+ GraphView::InputPort input_port = graph.GetInputPort(repeat_node.name(), 0);
+ NodeDef* node2 = graph.GetRegularFanin(input_port).node;
+ if (node2->op() != "ShuffleDataset") {
+ continue;
+ }
+ // Use a more descriptive variable name now that we know the node type.
+ const NodeDef& shuffle_node = *node2;
+
+ NodeDef* shuffle_and_repeat_node =
+ graph.AddNode(make_shuffle_and_repeat_node(shuffle_node, repeat_node));
+ graph.ReplaceInput(repeat_node, *shuffle_and_repeat_node);
// Mark the `Shuffle` and `Repeat` nodes for removal.
- nodes_to_delete.insert(shuffle_node->name());
+ nodes_to_delete.insert(shuffle_node.name());
nodes_to_delete.insert(repeat_node.name());
-
- graph_utils::ReplaceInput(repeat_node, *new_node, &graph);
}
- TF_RETURN_IF_ERROR(graph_utils::DeleteNodes(nodes_to_delete, output));
+
+ graph.DeleteNodes(nodes_to_delete);
return Status::OK();
}
diff --git a/tensorflow/core/grappler/optimizers/data/shuffle_and_repeat_fusion_test.cc b/tensorflow/core/grappler/optimizers/data/shuffle_and_repeat_fusion_test.cc
index e89675efb7..a2e470e511 100644
--- a/tensorflow/core/grappler/optimizers/data/shuffle_and_repeat_fusion_test.cc
+++ b/tensorflow/core/grappler/optimizers/data/shuffle_and_repeat_fusion_test.cc
@@ -27,7 +27,7 @@ namespace {
TEST(ShuffleAndRepeatFusionTest, FuseShuffleAndRepeatNodesIntoOne) {
GrapplerItem item;
- GraphDef *graph = &item.graph;
+ MutableGraphView graph(&item.graph);
std::vector<std::pair<string, AttrValue>> common_attrs(2);
AttrValue shapes_attr;
@@ -37,52 +37,44 @@ TEST(ShuffleAndRepeatFusionTest, FuseShuffleAndRepeatNodesIntoOne) {
SetAttrValue("output_types", &types_attr);
common_attrs[1] = std::make_pair("output_types", types_attr);
- NodeDef *start_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(0, graph, &start_node));
- NodeDef *stop_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(10, graph, &stop_node));
- NodeDef *step_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(1, graph, &step_node));
+ NodeDef *start_node = graph_utils::AddScalarConstNode<int64>(0, &graph);
+ NodeDef *stop_node = graph_utils::AddScalarConstNode<int64>(10, &graph);
+ NodeDef *step_node = graph_utils::AddScalarConstNode<int64>(1, &graph);
std::vector<string> range_inputs(3);
range_inputs[0] = start_node->name();
range_inputs[1] = stop_node->name();
range_inputs[2] = step_node->name();
- NodeDef *range_node;
- TF_ASSERT_OK(graph_utils::AddNode("", "RangeDataset", range_inputs,
- common_attrs, graph, &range_node));
-
- NodeDef *buffer_size_node;
- TF_ASSERT_OK(
- graph_utils::AddScalarConstNode<int64>(128, graph, &buffer_size_node));
- NodeDef *seed_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(-1, graph, &seed_node));
- NodeDef *seed2_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(-1, graph, &seed2_node));
+ NodeDef *range_node = graph_utils::AddNode("", "RangeDataset", range_inputs,
+ common_attrs, &graph);
+
+ NodeDef *buffer_size_node =
+ graph_utils::AddScalarConstNode<int64>(128, &graph);
+ NodeDef *seed_node = graph_utils::AddScalarConstNode<int64>(-1, &graph);
+ NodeDef *seed2_node = graph_utils::AddScalarConstNode<int64>(-1, &graph);
std::vector<string> shuffle_inputs(4);
shuffle_inputs[0] = range_node->name();
shuffle_inputs[1] = buffer_size_node->name();
shuffle_inputs[2] = seed_node->name();
shuffle_inputs[3] = seed2_node->name();
- NodeDef *shuffle_node;
- TF_ASSERT_OK(graph_utils::AddNode("", "ShuffleDataset", shuffle_inputs,
- common_attrs, graph, &shuffle_node));
+ NodeDef *shuffle_node = graph_utils::AddNode(
+ "", "ShuffleDataset", shuffle_inputs, common_attrs, &graph);
- NodeDef *count_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(-1, graph, &count_node));
+ NodeDef *count_node = graph_utils::AddScalarConstNode<int64>(-1, &graph);
std::vector<string> repeat_inputs(2);
repeat_inputs[0] = shuffle_node->name();
repeat_inputs[1] = count_node->name();
- NodeDef *repeat_node;
- TF_ASSERT_OK(graph_utils::AddNode("", "RepeatDataset", repeat_inputs,
- common_attrs, graph, &repeat_node));
+ NodeDef *repeat_node = graph_utils::AddNode(
+ "", "RepeatDataset", repeat_inputs, common_attrs, &graph);
ShuffleAndRepeatFusion optimizer;
GraphDef output;
TF_ASSERT_OK(optimizer.Optimize(nullptr, item, &output));
- EXPECT_FALSE(graph_utils::ContainsNodeWithName(shuffle_node->name(), output));
- EXPECT_FALSE(graph_utils::ContainsNodeWithName(repeat_node->name(), output));
+ EXPECT_FALSE(
+ graph_utils::ContainsGraphNodeWithName(shuffle_node->name(), output));
+ EXPECT_FALSE(
+ graph_utils::ContainsGraphNodeWithName(repeat_node->name(), output));
EXPECT_TRUE(
graph_utils::ContainsNodeWithOp("ShuffleAndRepeatDataset", output));
NodeDef shuffle_and_repeat_node = output.node(
@@ -103,7 +95,7 @@ TEST(ShuffleAndRepeatFusionTest, FuseShuffleAndRepeatNodesIntoOne) {
TEST(ShuffleAndRepeatFusionTest, NoChange) {
GrapplerItem item;
- GraphDef *graph = &item.graph;
+ MutableGraphView graph(&item.graph);
std::vector<std::pair<string, AttrValue>> common_attrs(2);
AttrValue shapes_attr;
@@ -113,35 +105,29 @@ TEST(ShuffleAndRepeatFusionTest, NoChange) {
SetAttrValue("output_types", &types_attr);
common_attrs[1] = std::make_pair("output_types", types_attr);
- NodeDef *start_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(0, graph, &start_node));
- NodeDef *stop_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(10, graph, &stop_node));
- NodeDef *step_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(1, graph, &step_node));
+ NodeDef *start_node = graph_utils::AddScalarConstNode<int64>(0, &graph);
+ NodeDef *stop_node = graph_utils::AddScalarConstNode<int64>(10, &graph);
+ NodeDef *step_node = graph_utils::AddScalarConstNode<int64>(1, &graph);
std::vector<string> range_inputs(3);
range_inputs[0] = start_node->name();
range_inputs[1] = stop_node->name();
range_inputs[2] = step_node->name();
- NodeDef *range_node;
- TF_ASSERT_OK(graph_utils::AddNode("", "RangeDataset", range_inputs,
- common_attrs, graph, &range_node));
+ NodeDef *range_node = graph_utils::AddNode("", "RangeDataset", range_inputs,
+ common_attrs, &graph);
- NodeDef *count_node;
- TF_ASSERT_OK(graph_utils::AddScalarConstNode<int64>(-1, graph, &count_node));
+ NodeDef *count_node = graph_utils::AddScalarConstNode<int64>(-1, &graph);
std::vector<string> repeat_inputs(2);
repeat_inputs[0] = range_node->name();
repeat_inputs[1] = count_node->name();
- NodeDef *repeat_node;
- TF_ASSERT_OK(graph_utils::AddNode("", "RepeatDataset", repeat_inputs,
- common_attrs, graph, &repeat_node));
+ graph_utils::AddNode("", "RepeatDataset", repeat_inputs, common_attrs,
+ &graph);
ShuffleAndRepeatFusion optimizer;
GraphDef output;
TF_ASSERT_OK(optimizer.Optimize(nullptr, item, &output));
- EXPECT_TRUE(graph_utils::Compare(*graph, output));
+ EXPECT_TRUE(graph_utils::Compare(*graph.GetGraph(), output));
}
} // namespace
diff --git a/tensorflow/core/grappler/optimizers/dependency_optimizer.cc b/tensorflow/core/grappler/optimizers/dependency_optimizer.cc
index fdd82b9603..bb14ce310d 100644
--- a/tensorflow/core/grappler/optimizers/dependency_optimizer.cc
+++ b/tensorflow/core/grappler/optimizers/dependency_optimizer.cc
@@ -24,6 +24,7 @@ limitations under the License.
#include "tensorflow/core/grappler/grappler_item.h"
#include "tensorflow/core/grappler/op_types.h"
#include "tensorflow/core/grappler/optimizers/constant_folding.h"
+#include "tensorflow/core/grappler/utils.h"
#include "tensorflow/core/grappler/utils/topological_sort.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/core/stringpiece.h"
@@ -53,16 +54,6 @@ bool RemoveInput(NodeDef* node, const string& input, NodeMap* node_map) {
return removed_input;
}
-void DeleteNodes(const std::set<int>& nodes_to_delete, GraphDef* graph) {
- int last = graph->node_size() - 1;
- for (auto it = nodes_to_delete.rbegin(); it != nodes_to_delete.rend(); ++it) {
- const int index = *it;
- graph->mutable_node()->SwapElements(index, last);
- last--;
- }
- graph->mutable_node()->DeleteSubrange(last + 1, nodes_to_delete.size());
-}
-
} // namespace
bool DependencyOptimizer::SafeToRemoveIdentity(const NodeDef& node) const {
@@ -441,7 +432,7 @@ Status DependencyOptimizer::OptimizeDependencies() {
if (fetch_nodes_known_) {
VLOG(1) << "Deleted " << nodes_to_delete.size() << " out of "
<< optimized_graph_->node_size() << " nodes.";
- DeleteNodes(nodes_to_delete, optimized_graph_);
+ EraseNodesFromGraph(nodes_to_delete, optimized_graph_);
node_map_.reset(new NodeMap(optimized_graph_));
BuildNodeToIdx();
}
diff --git a/tensorflow/core/grappler/optimizers/evaluation_utils.cc b/tensorflow/core/grappler/optimizers/evaluation_utils.cc
new file mode 100644
index 0000000000..00ad7494f4
--- /dev/null
+++ b/tensorflow/core/grappler/optimizers/evaluation_utils.cc
@@ -0,0 +1,120 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/core/grappler/optimizers/evaluation_utils.h"
+
+#include "tensorflow/core/lib/core/threadpool.h"
+#include "tensorflow/core/platform/cpu_info.h"
+#include "tensorflow/core/platform/denormal.h"
+#include "tensorflow/core/platform/setround.h"
+#include "tensorflow/core/public/version.h"
+
+namespace tensorflow {
+namespace grappler {
+using TensorVector = gtl::InlinedVector<TensorValue, 4>;
+
+namespace {
+class EigenThreadPoolWrapper : public Eigen::ThreadPoolInterface {
+ public:
+ explicit EigenThreadPoolWrapper(thread::ThreadPool* pool) : pool_(pool) {}
+ ~EigenThreadPoolWrapper() override {}
+ void Schedule(std::function<void()> fn) override {
+ auto wrapped = [=]() {
+ // TensorFlow flushes denormals to zero and rounds to nearest, so we do
+ // the same here.
+ port::ScopedFlushDenormal flush;
+ port::ScopedSetRound round(FE_TONEAREST);
+ fn();
+ };
+ pool_->Schedule(std::move(wrapped));
+ }
+ int NumThreads() const override { return pool_->NumThreads(); }
+ int CurrentThreadId() const override { return pool_->CurrentThreadId(); }
+
+ private:
+ thread::ThreadPool* pool_ = nullptr;
+};
+
+} // namespace
+
+DeviceSimple::DeviceSimple() : DeviceBase(Env::Default()) {
+ eigen_worker_threads_.num_threads = port::NumSchedulableCPUs();
+ eigen_worker_threads_.workers = new thread::ThreadPool(
+ Env::Default(), "evaluation_utils", eigen_worker_threads_.num_threads);
+ eigen_threadpool_wrapper_.reset(
+ new EigenThreadPoolWrapper(eigen_worker_threads_.workers));
+ eigen_device_.reset(new Eigen::ThreadPoolDevice(
+ eigen_threadpool_wrapper_.get(), eigen_worker_threads_.num_threads));
+ set_tensorflow_cpu_worker_threads(&eigen_worker_threads_);
+ set_eigen_cpu_device(eigen_device_.get());
+}
+
+DeviceSimple::~DeviceSimple() {
+ eigen_threadpool_wrapper_.reset();
+ eigen_device_.reset();
+ delete eigen_worker_threads_.workers;
+}
+
+Status DeviceSimple::MakeTensorFromProto(const TensorProto& tensor_proto,
+ const AllocatorAttributes alloc_attrs,
+ Tensor* tensor) {
+ Tensor parsed(tensor_proto.dtype());
+ if (!parsed.FromProto(cpu_allocator(), tensor_proto)) {
+ return errors::InvalidArgument("Cannot parse tensor from tensor_proto.");
+ }
+ *tensor = parsed;
+ return Status::OK();
+}
+
+Status EvaluateNode(const NodeDef& node, const TensorVector& inputs,
+ DeviceBase* cpu_device, ResourceMgr* resource_mgr,
+ TensorVector* output) {
+ Status status;
+ std::unique_ptr<DeviceBase> device;
+ if (cpu_device == nullptr) {
+ device.reset(new DeviceSimple());
+ cpu_device = device.get();
+ }
+
+ std::unique_ptr<OpKernel> op_kernel(
+ CreateOpKernel("CPU", cpu_device, cpu_device->GetAllocator({}), node,
+ TF_GRAPH_DEF_VERSION, &status));
+ TF_RETURN_IF_ERROR(status);
+ OpKernelContext::Params params;
+ params.device = cpu_device;
+ params.frame_iter = FrameAndIter(0, 0);
+ params.inputs = &inputs;
+ params.op_kernel = op_kernel.get();
+ params.resource_manager = resource_mgr;
+
+ gtl::InlinedVector<AllocatorAttributes, 4> output_attrs;
+ const int num_outputs = op_kernel->num_outputs();
+ for (int i = 0; i < num_outputs; i++) {
+ AllocatorAttributes attr;
+ attr.set_on_host(true);
+ output_attrs.push_back(attr);
+ }
+ params.output_attr_array = output_attrs.data();
+
+ OpKernelContext op_context(&params);
+ op_kernel->Compute(&op_context);
+ for (int i = 0; i < num_outputs; i++) {
+ output->push_back(op_context.release_output(i));
+ }
+ return op_context.status();
+}
+
+} // end namespace grappler
+} // end namespace tensorflow
diff --git a/tensorflow/core/grappler/optimizers/evaluation_utils.h b/tensorflow/core/grappler/optimizers/evaluation_utils.h
new file mode 100644
index 0000000000..8414b5b8ca
--- /dev/null
+++ b/tensorflow/core/grappler/optimizers/evaluation_utils.h
@@ -0,0 +1,61 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#ifndef TENSORFLOW_CORE_GRAPPLER_OPTIMIZERS_EVALUATION_UTILS_H_
+#define TENSORFLOW_CORE_GRAPPLER_OPTIMIZERS_EVALUATION_UTILS_H_
+
+#define EIGEN_USE_THREADS
+
+#include "tensorflow/core/framework/device_base.h"
+#include "tensorflow/core/framework/node_def.pb.h"
+#include "tensorflow/core/framework/op_kernel.h"
+#include "tensorflow/core/lib/gtl/inlined_vector.h"
+
+namespace Eigen {
+class ThreadPoolInterface;
+class ThreadPoolWrapper;
+} // namespace Eigen
+
+namespace tensorflow {
+namespace grappler {
+
+class DeviceSimple : public DeviceBase {
+ public:
+ DeviceSimple();
+ ~DeviceSimple();
+
+ Status MakeTensorFromProto(const TensorProto& tensor_proto,
+ const AllocatorAttributes alloc_attrs,
+ Tensor* tensor) override;
+
+ Allocator* GetAllocator(AllocatorAttributes attr) override {
+ return cpu_allocator();
+ }
+
+ private:
+ DeviceBase::CpuWorkerThreads eigen_worker_threads_;
+ std::unique_ptr<Eigen::ThreadPoolInterface> eigen_threadpool_wrapper_;
+ std::unique_ptr<Eigen::ThreadPoolDevice> eigen_device_;
+};
+
+Status EvaluateNode(const NodeDef& node,
+ const gtl::InlinedVector<TensorValue, 4>& inputs,
+ DeviceBase* cpu_device, ResourceMgr* resource_mgr,
+ gtl::InlinedVector<TensorValue, 4>* output);
+
+} // end namespace grappler
+} // end namespace tensorflow
+
+#endif // TENSORFLOW_CORE_GRAPPLER_OPTIMIZERS_EVALUATION_UTILS_H_
diff --git a/tensorflow/core/grappler/optimizers/evaluation_utils_test.cc b/tensorflow/core/grappler/optimizers/evaluation_utils_test.cc
new file mode 100644
index 0000000000..17b42490d7
--- /dev/null
+++ b/tensorflow/core/grappler/optimizers/evaluation_utils_test.cc
@@ -0,0 +1,63 @@
+#include "tensorflow/core/platform/cpu_info.h"
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#define EIGEN_USE_THREADS
+
+#include "third_party/eigen3/unsupported/Eigen/CXX11/ThreadPool"
+#include "tensorflow/core/framework/tensor.h"
+#include "tensorflow/core/framework/tensor.pb.h"
+#include "tensorflow/core/grappler/optimizers/evaluation_utils.h"
+#include "tensorflow/core/lib/core/status_test_util.h"
+#include "tensorflow/core/platform/test.h"
+
+namespace tensorflow {
+namespace grappler {
+
+TEST(EvaluationUtilsTest, DeviceSimple_BasicProperties) {
+ DeviceSimple dsimple;
+ ASSERT_TRUE(dsimple.has_eigen_cpu_device());
+ EXPECT_EQ(dsimple.eigen_cpu_device()->numThreads(),
+ port::NumSchedulableCPUs());
+ const Eigen::ThreadPoolInterface* pool =
+ dsimple.eigen_cpu_device()->getPool();
+ ASSERT_NE(pool, nullptr);
+}
+
+TEST(EvaluationUtilsTest, DeviceSimple_MakeTensorFromProto) {
+ DeviceSimple dsimple;
+
+ TensorProto proto;
+ Tensor tensor;
+ EXPECT_FALSE(dsimple.MakeTensorFromProto(proto, {}, &tensor).ok());
+
+ Tensor original(tensorflow::DT_INT16, TensorShape{4, 2});
+ original.flat<int16>().setRandom();
+
+ original.AsProtoTensorContent(&proto);
+ TF_ASSERT_OK(dsimple.MakeTensorFromProto(proto, {}, &tensor));
+
+ ASSERT_EQ(tensor.dtype(), original.dtype());
+ ASSERT_EQ(tensor.shape(), original.shape());
+
+ auto buf0 = original.flat<int16>();
+ auto buf1 = tensor.flat<int16>();
+ ASSERT_EQ(buf0.size(), buf1.size());
+ for (int i = 0; i < buf0.size(); ++i) {
+ EXPECT_EQ(buf0(i), buf1(i));
+ }
+}
+} // namespace grappler
+} // namespace tensorflow
diff --git a/tensorflow/core/grappler/optimizers/loop_optimizer.cc b/tensorflow/core/grappler/optimizers/loop_optimizer.cc
index 9627ed7323..223efd2670 100644
--- a/tensorflow/core/grappler/optimizers/loop_optimizer.cc
+++ b/tensorflow/core/grappler/optimizers/loop_optimizer.cc
@@ -22,20 +22,26 @@ limitations under the License.
#include <unordered_set>
#include <vector>
+#include "tensorflow/core/common_runtime/device.h"
+#include "tensorflow/core/framework/allocator.h"
#include "tensorflow/core/framework/attr_value.pb.h"
#include "tensorflow/core/framework/node_def.pb.h"
#include "tensorflow/core/framework/op.h"
+#include "tensorflow/core/framework/tensor.pb.h"
#include "tensorflow/core/framework/types.h"
#include "tensorflow/core/grappler/graph_view.h"
#include "tensorflow/core/grappler/grappler_item.h"
#include "tensorflow/core/grappler/op_types.h"
#include "tensorflow/core/grappler/optimizers/constant_folding.h"
+#include "tensorflow/core/grappler/optimizers/evaluation_utils.h"
#include "tensorflow/core/grappler/utils.h"
#include "tensorflow/core/grappler/utils/frame.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/core/stringpiece.h"
+#include "tensorflow/core/lib/gtl/inlined_vector.h"
#include "tensorflow/core/lib/strings/strcat.h"
#include "tensorflow/core/platform/tensor_coding.h"
+#include "tensorflow/core/public/version.h"
#include "tensorflow/core/util/device_name_utils.h"
#include "tensorflow/core/util/saved_tensor_slice_util.h"
@@ -45,6 +51,8 @@ namespace tensorflow {
namespace grappler {
namespace {
+using TensorVector = gtl::InlinedVector<TensorValue, 4>;
+
class LoopInvariantNodeMotionOptimizer {
public:
explicit LoopInvariantNodeMotionOptimizer(GraphDef* optimized_graph)
@@ -504,8 +512,180 @@ Status RemoveStackOps(const std::unordered_set<string>& nodes_to_preserve,
return Status::OK();
}
-Status RemoveDeadBranches(const std::unordered_set<string>& nodes_to_preserve,
- GraphDef* optimized_graph) {
+bool IsSimpleBinaryOperator(const NodeDef& node) {
+ return (IsLess(node) || IsLessEqual(node) || IsGreater(node) ||
+ IsGreaterEqual(node) || IsEqual(node));
+}
+
+Status EvaluateBoolOpForConstantOperands(const NodeDef& op_node,
+ const NodeDef& constant_operand_0,
+ const NodeDef& constant_operand_1,
+ DeviceBase* cpu_device,
+ ResourceMgr* resource_mgr,
+ bool* value) {
+ TensorVector inputs;
+
+ const TensorProto& raw_val_0 = constant_operand_0.attr().at("value").tensor();
+ Tensor value_0(raw_val_0.dtype(), raw_val_0.tensor_shape());
+ CHECK(value_0.FromProto(raw_val_0));
+ inputs.emplace_back(&value_0);
+ const TensorProto& raw_val_1 = constant_operand_1.attr().at("value").tensor();
+ Tensor value_1(raw_val_1.dtype(), raw_val_1.tensor_shape());
+ CHECK(value_1.FromProto(raw_val_1));
+ inputs.emplace_back(&value_1);
+
+ TensorVector outputs;
+ TF_RETURN_IF_ERROR(
+ EvaluateNode(op_node, inputs, cpu_device, resource_mgr, &outputs));
+
+ if (outputs.size() != 1 || outputs[0].tensor == nullptr) {
+ return Status(error::INVALID_ARGUMENT, "Expected one output.");
+ }
+ *value = outputs[0].tensor->scalar<bool>()();
+ delete outputs[0].tensor;
+
+ return Status::OK();
+}
+
+Status CheckForDeadFanout(const GraphView& view, const NodeDef& switch_node,
+ const NodeMap& node_map,
+ bool is_optimization_aggressive,
+ DeviceBase* cpu_device, ResourceMgr* resource_mgr,
+ bool* has_dead_fanout, int* dead_fanout) {
+ *has_dead_fanout = false;
+ GraphView::InputPort switch_loopcond_port(&switch_node, 1);
+ NodeDef* switch_predicate = view.GetRegularFanin(switch_loopcond_port).node;
+
+ // CASE 1: Control is a constant.
+ if (IsConstant(*switch_predicate)) {
+ Tensor selector;
+ CHECK(selector.FromProto(switch_predicate->attr().at("value").tensor()));
+ *has_dead_fanout = true;
+ *dead_fanout = selector.scalar<bool>()() ? 0 : 1;
+ }
+
+ GraphView::InputPort switch_input_port(&switch_node, 0);
+ NodeDef* switch_input = view.GetRegularFanin(switch_input_port).node;
+
+ // CASE 2: Zero-iteration while loop.
+ // We check if its a while loop such that the condition is a simple binary
+ // operator which returns false for the initialization value.
+ // TODO(srjoglekar): Improve to work with arbitrary predicate subgraphs.
+ if (!is_optimization_aggressive || !IsMerge(*switch_input)) {
+ return Status::OK();
+ }
+
+ // Find the boolean Op from predicate node.
+ NodeDef* switch_ctrl_node = nullptr;
+ for (int i = 0; i < switch_predicate->input().size(); ++i) {
+ NodeDef* node = node_map.GetNode(switch_predicate->input(i));
+ if (IsSimpleBinaryOperator(*node)) {
+ switch_ctrl_node = node;
+ }
+ }
+ if (switch_ctrl_node == nullptr) {
+ return Status::OK();
+ }
+ // Find the Merge node & the Constant Operand to the condition node, if
+ // available.
+ NodeDef* merge_node = nullptr;
+ NodeDef* constant_ctrl_input = nullptr;
+ int constant_index = 0;
+ for (int i = 0; i < switch_ctrl_node->input().size(); ++i) {
+ NodeDef* node = node_map.GetNode(switch_ctrl_node->input(i));
+ if (IsMerge(*node)) {
+ merge_node = node;
+ }
+ if (IsConstant(*node)) {
+ constant_ctrl_input = node;
+ constant_index = i;
+ }
+ }
+ if (merge_node == nullptr || constant_ctrl_input == nullptr) {
+ return Status::OK();
+ }
+ // Find Enter.
+ // TODO(srjoglekar): Reconcile this with the optimization in
+ // ConstantFolding::MoveConstantsPastEnter
+ NodeDef* enter_node = nullptr;
+ for (const auto& input : merge_node->input()) {
+ NodeDef* node = node_map.GetNode(input);
+ if (IsEnter(*node)) {
+ enter_node = node;
+ }
+ }
+ if (enter_node == nullptr) {
+ return Status::OK();
+ }
+ // Find the initialization constant.
+ NodeDef* constant_init_node = nullptr;
+ for (const auto& input : enter_node->input()) {
+ NodeDef* node = node_map.GetNode(input);
+ if (IsConstant(*node)) {
+ constant_init_node = node;
+ }
+ }
+ if (constant_init_node == nullptr) {
+ return Status::OK();
+ }
+
+ // Check if there will be 0 iterations. This will only happen if the condition
+ // evaluates to false with respect to the initialization value.
+ NodeDef* operand_0 =
+ constant_index ? constant_init_node : constant_ctrl_input;
+ NodeDef* operand_1 =
+ constant_index ? constant_ctrl_input : constant_init_node;
+ bool constant_switch_value;
+ TF_RETURN_IF_ERROR(EvaluateBoolOpForConstantOperands(
+ *switch_ctrl_node, *operand_0, *operand_1, cpu_device, resource_mgr,
+ &constant_switch_value));
+ if (constant_switch_value == false) {
+ *has_dead_fanout = true;
+ *dead_fanout = 1;
+ }
+ return Status::OK();
+}
+
+} // namespace
+
+LoopOptimizer::LoopOptimizer()
+ : opt_level_(RewriterConfig::ON),
+ cpu_device_(nullptr),
+ options_(LoopOptimizerOptions::Default(RewriterConfig::ON)) {}
+
+LoopOptimizer::LoopOptimizer(RewriterConfig::Toggle opt_level,
+ DeviceBase* cpu_device)
+ : opt_level_(opt_level),
+ cpu_device_(cpu_device),
+ options_(LoopOptimizerOptions::Default(RewriterConfig::ON)) {
+ resource_mgr_.reset(new ResourceMgr());
+}
+
+Status LoopOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item,
+ GraphDef* optimized_graph) {
+ *optimized_graph = item.graph;
+ // Set up helper data structures.
+ if (options_.enable_loop_invariant_node_motion) {
+ LoopInvariantNodeMotionOptimizer linm_optimizer(optimized_graph);
+ TF_RETURN_IF_ERROR(linm_optimizer.Optimize());
+ }
+ if (options_.enable_stack_push_removal) {
+ TF_RETURN_IF_ERROR(RemoveStackOps(item.NodesToPreserve(), optimized_graph));
+ }
+ if (options_.enable_dead_branch_removal) {
+ // TODO(srjoglekar): Figure out if we can optimize NodeMap creations across
+ // optimizer passes.
+ NodeMap node_map(optimized_graph);
+ TF_RETURN_IF_ERROR(
+ RemoveDeadBranches(item.NodesToPreserve(), node_map, optimized_graph));
+ }
+
+ return Status::OK();
+}
+
+Status LoopOptimizer::RemoveDeadBranches(
+ const std::unordered_set<string>& nodes_to_preserve,
+ const NodeMap& node_map, GraphDef* optimized_graph) {
std::unordered_set<const NodeDef*> dead_nodes;
std::unordered_map<NodeDef*, std::set<int>> dead_merge_inputs;
// TODO(bsteiner): also rewrite switches as identity. For now we just record
@@ -521,14 +701,15 @@ Status RemoveDeadBranches(const std::unordered_set<string>& nodes_to_preserve,
if (nodes_to_preserve.find(node.name()) != nodes_to_preserve.end()) {
continue;
}
- GraphView::InputPort ctrl_port(&node, 1);
- GraphView::OutputPort ctrl_node = view.GetRegularFanin(ctrl_port);
- if (!IsConstant(*ctrl_node.node)) {
+
+ int dead_fanout;
+ bool has_dead_fanout;
+ TF_RETURN_IF_ERROR(CheckForDeadFanout(
+ view, node, node_map, opt_level_ == RewriterConfig::AGGRESSIVE,
+ cpu_device_, resource_mgr_.get(), &has_dead_fanout, &dead_fanout));
+ if (!has_dead_fanout) {
continue;
}
- Tensor selector;
- CHECK(selector.FromProto(ctrl_node.node->attr().at("value").tensor()));
- const int dead_fanout = selector.scalar<bool>()() ? 0 : 1;
GraphView::OutputPort dead(const_cast<NodeDef*>(&node), dead_fanout);
identity_switches.insert(dead);
@@ -616,15 +797,13 @@ Status RemoveDeadBranches(const std::unordered_set<string>& nodes_to_preserve,
}
}
- int last = optimized_graph->node_size() - 1;
- for (int i = optimized_graph->node_size() - 1; i >= 0; --i) {
- NodeDef* node = optimized_graph->mutable_node(i);
- if (dead_nodes.find(node) != dead_nodes.end()) {
- optimized_graph->mutable_node()->SwapElements(i, last);
- last--;
- }
+ std::vector<int> nodes_idx_to_delete;
+ nodes_idx_to_delete.reserve(dead_nodes.size());
+ for (int i = 0; i < optimized_graph->node_size(); ++i) {
+ if (dead_nodes.count(&optimized_graph->node(i)))
+ nodes_idx_to_delete.push_back(i);
}
- optimized_graph->mutable_node()->DeleteSubrange(last + 1, dead_nodes.size());
+ EraseNodesFromGraph(std::move(nodes_idx_to_delete), optimized_graph);
for (const auto& itr : dead_merge_inputs) {
NodeDef* dead_node = itr.first;
@@ -642,27 +821,6 @@ Status RemoveDeadBranches(const std::unordered_set<string>& nodes_to_preserve,
return Status::OK();
}
-} // namespace
-
-Status LoopOptimizer::Optimize(Cluster* cluster, const GrapplerItem& item,
- GraphDef* optimized_graph) {
- *optimized_graph = item.graph;
- // Set up helper data structures.
- if (options_.enable_loop_invariant_node_motion) {
- LoopInvariantNodeMotionOptimizer linm_optimizer(optimized_graph);
- TF_RETURN_IF_ERROR(linm_optimizer.Optimize());
- }
- if (options_.enable_stack_push_removal) {
- TF_RETURN_IF_ERROR(RemoveStackOps(item.NodesToPreserve(), optimized_graph));
- }
- if (options_.enable_dead_branch_removal) {
- TF_RETURN_IF_ERROR(
- RemoveDeadBranches(item.NodesToPreserve(), optimized_graph));
- }
-
- return Status::OK();
-}
-
void LoopOptimizer::Feedback(Cluster* /*cluster*/, const GrapplerItem& /*item*/,
const GraphDef& /*optimized_graph*/,
double /*result*/) {
diff --git a/tensorflow/core/grappler/optimizers/loop_optimizer.h b/tensorflow/core/grappler/optimizers/loop_optimizer.h
index 85b8e65543..7c04f55381 100644
--- a/tensorflow/core/grappler/optimizers/loop_optimizer.h
+++ b/tensorflow/core/grappler/optimizers/loop_optimizer.h
@@ -30,12 +30,10 @@ constexpr char kLoopOptimizer[] = "LoopOptimizer";
class LoopOptimizer : public GraphOptimizer {
public:
- LoopOptimizer()
- : opt_level_(RewriterConfig::ON),
- options_(LoopOptimizerOptions::Default(RewriterConfig::ON)) {}
- explicit LoopOptimizer(RewriterConfig::Toggle opt_level)
- : opt_level_(opt_level),
- options_(LoopOptimizerOptions::Default(RewriterConfig::ON)) {}
+ LoopOptimizer();
+
+ explicit LoopOptimizer(RewriterConfig::Toggle opt_level,
+ DeviceBase* cpu_device);
~LoopOptimizer() override {}
@@ -62,8 +60,13 @@ class LoopOptimizer : public GraphOptimizer {
}
};
+ Status RemoveDeadBranches(const std::unordered_set<string>& nodes_to_preserve,
+ const NodeMap& node_map, GraphDef* optimized_graph);
+
RewriterConfig::Toggle opt_level_;
+ DeviceBase* cpu_device_;
LoopOptimizerOptions options_;
+ std::unique_ptr<ResourceMgr> resource_mgr_;
};
} // end namespace grappler
diff --git a/tensorflow/core/grappler/optimizers/loop_optimizer_test.cc b/tensorflow/core/grappler/optimizers/loop_optimizer_test.cc
index 6fd177b710..f5fe28d4ba 100644
--- a/tensorflow/core/grappler/optimizers/loop_optimizer_test.cc
+++ b/tensorflow/core/grappler/optimizers/loop_optimizer_test.cc
@@ -16,6 +16,7 @@ limitations under the License.
#include "tensorflow/core/grappler/optimizers/loop_optimizer.h"
#include "tensorflow/cc/ops/standard_ops.h"
#include "tensorflow/core/framework/node_def.pb.h"
+#include "tensorflow/core/framework/tensor_testutil.h"
#include "tensorflow/core/grappler/grappler_item.h"
#include "tensorflow/core/grappler/inputs/trivial_test_graph_input_yielder.h"
#include "tensorflow/core/grappler/utils.h"
@@ -589,7 +590,7 @@ TEST_F(LoopOptimizerTest, RemovePushWithoutMatchingPop) {
}
}
-TEST_F(LoopOptimizerTest, RemoveDeadBranches) {
+TEST_F(LoopOptimizerTest, RemoveDeadBranches_ConstantCondition) {
Scope scope = Scope::NewRootScope();
Output v_in = ops::Variable(scope.WithOpName("v_in"), {3}, DT_FLOAT);
@@ -639,7 +640,7 @@ TEST_F(LoopOptimizerTest, RemoveDeadBranches) {
TF_CHECK_OK(scope.ToGraphDef(&item.graph));
- LoopOptimizer optimizer(RewriterConfig::AGGRESSIVE);
+ LoopOptimizer optimizer(RewriterConfig::AGGRESSIVE, nullptr);
GraphDef output;
Status status = optimizer.Optimize(nullptr, item, &output);
TF_CHECK_OK(status);
@@ -696,5 +697,237 @@ TEST_F(LoopOptimizerTest, RemoveDeadBranches) {
}
}
+TEST_F(LoopOptimizerTest, RemoveDeadBranches_ZeroIterWhile) {
+ const string gdef_ascii = R"EOF(
+node {
+ name: "Const"
+ op: "Const"
+ attr {
+ key: "dtype"
+ value {
+ type: DT_INT32
+ }
+ }
+ attr {
+ key: "value"
+ value {
+ tensor {
+ dtype: DT_INT32
+ tensor_shape {
+ }
+ int_val: 20
+ }
+ }
+ }
+}
+node {
+ name: "while/Enter"
+ op: "Enter"
+ input: "Const"
+ attr {
+ key: "T"
+ value {
+ type: DT_INT32
+ }
+ }
+ attr {
+ key: "frame_name"
+ value {
+ s: "while/while/"
+ }
+ }
+ attr {
+ key: "is_constant"
+ value {
+ b: false
+ }
+ }
+ attr {
+ key: "parallel_iterations"
+ value {
+ i: 1
+ }
+ }
+}
+node {
+ name: "while/Merge"
+ op: "Merge"
+ input: "while/Enter"
+ input: "while/NextIteration"
+ attr {
+ key: "N"
+ value {
+ i: 2
+ }
+ }
+ attr {
+ key: "T"
+ value {
+ type: DT_INT32
+ }
+ }
+}
+node {
+ name: "while/Less/y"
+ op: "Const"
+ input: "^while/Merge"
+ attr {
+ key: "dtype"
+ value {
+ type: DT_INT32
+ }
+ }
+ attr {
+ key: "value"
+ value {
+ tensor {
+ dtype: DT_INT32
+ tensor_shape {
+ }
+ int_val: 10
+ }
+ }
+ }
+}
+node {
+ name: "while/Less"
+ op: "Less"
+ input: "while/Merge"
+ input: "while/Less/y"
+ attr {
+ key: "T"
+ value {
+ type: DT_INT32
+ }
+ }
+}
+node {
+ name: "while/LoopCond"
+ op: "LoopCond"
+ input: "while/Less"
+}
+node {
+ name: "while/Switch"
+ op: "Switch"
+ input: "while/Merge"
+ input: "while/LoopCond"
+ attr {
+ key: "T"
+ value {
+ type: DT_INT32
+ }
+ }
+ attr {
+ key: "_class"
+ value {
+ list {
+ s: "loc:@while/Merge"
+ }
+ }
+ }
+}
+node {
+ name: "while/Identity"
+ op: "Identity"
+ input: "while/Switch:1"
+ attr {
+ key: "T"
+ value {
+ type: DT_INT32
+ }
+ }
+}
+node {
+ name: "while/add/y"
+ op: "Const"
+ input: "^while/Identity"
+ attr {
+ key: "dtype"
+ value {
+ type: DT_INT32
+ }
+ }
+ attr {
+ key: "value"
+ value {
+ tensor {
+ dtype: DT_INT32
+ tensor_shape {
+ }
+ int_val: 1
+ }
+ }
+ }
+}
+node {
+ name: "while/add"
+ op: "Add"
+ input: "while/Identity"
+ input: "while/add/y"
+ attr {
+ key: "T"
+ value {
+ type: DT_INT32
+ }
+ }
+}
+node {
+ name: "while/NextIteration"
+ op: "NextIteration"
+ input: "while/add"
+ attr {
+ key: "T"
+ value {
+ type: DT_INT32
+ }
+ }
+}
+node {
+ name: "while/Exit"
+ op: "Exit"
+ input: "while/Switch"
+ attr {
+ key: "T"
+ value {
+ type: DT_INT32
+ }
+ }
+}
+versions {
+ producer: 21
+}
+ )EOF";
+
+ GrapplerItem item;
+ CHECK(protobuf::TextFormat::ParseFromString(gdef_ascii, &item.graph));
+ item.fetch = {"while/Exit"};
+ auto tensors_expected = EvaluateNodes(item.graph, item.fetch);
+ EXPECT_EQ(1, tensors_expected.size());
+
+ LoopOptimizer optimizer(RewriterConfig::AGGRESSIVE, nullptr);
+ GraphDef output;
+ Status status = optimizer.Optimize(nullptr, item, &output);
+ TF_CHECK_OK(status);
+ auto tensors_got = EvaluateNodes(output, item.fetch);
+ EXPECT_EQ(1, tensors_got.size());
+ test::ExpectTensorEqual<int32>(tensors_expected[0], tensors_got[0]);
+
+ int nodes_present = 0;
+ for (const NodeDef& node : output.node()) {
+ // All nodes connected to Switch's positive check should be pruned.
+ if (node.name() == "while/add") {
+ LOG(ERROR) << "while/add is present after optimization";
+ } else if (node.name() == "while/add/y") {
+ LOG(ERROR) << "while/add/y is present after optimization";
+ } else if (node.name() == "while/NextIteration") {
+ LOG(ERROR) << "while/NextIteration is present after optimization";
+ } else if (node.name() == "while/Identity") {
+ LOG(ERROR) << "while/Identity is present after optimization";
+ }
+ ++nodes_present;
+ }
+ EXPECT_EQ(8, nodes_present);
+}
+
} // namespace grappler
} // namespace tensorflow
diff --git a/tensorflow/core/grappler/optimizers/meta_optimizer.cc b/tensorflow/core/grappler/optimizers/meta_optimizer.cc
index c55f479451..96f6fe1e0b 100644
--- a/tensorflow/core/grappler/optimizers/meta_optimizer.cc
+++ b/tensorflow/core/grappler/optimizers/meta_optimizer.cc
@@ -87,7 +87,7 @@ std::unique_ptr<GraphOptimizer> MetaOptimizer::MakeNewOptimizer(
MK_OPT("memory", new MemoryOptimizer(RewriterConfig::MANUAL));
MK_OPT("arithmetic", new ArithmeticOptimizer(cfg_.arithmetic_optimization()));
MK_OPT("autoparallel", new AutoParallel(cfg_.auto_parallel().num_replicas()));
- MK_OPT("loop", new LoopOptimizer(cfg_.loop_optimization()));
+ MK_OPT("loop", new LoopOptimizer(cfg_.loop_optimization(), cpu_device_));
MK_OPT("dependency", new DependencyOptimizer(cfg_.dependency_optimization()));
MK_OPT("debug_stripper", new DebugStripper());
MK_OPT("scoped_allocator",
@@ -126,7 +126,8 @@ Status MetaOptimizer::InitializeOptimizers(
new ArithmeticOptimizer(cfg_.arithmetic_optimization()));
}
if (cfg_.loop_optimization() != RewriterConfig::OFF) {
- optimizers->emplace_back(new LoopOptimizer(cfg_.loop_optimization()));
+ optimizers->emplace_back(
+ new LoopOptimizer(cfg_.loop_optimization(), cpu_device_));
}
if (cfg_.dependency_optimization() != RewriterConfig::OFF) {
optimizers->emplace_back(
diff --git a/tensorflow/core/grappler/utils.cc b/tensorflow/core/grappler/utils.cc
index c8e63f95e1..153785d3b4 100644
--- a/tensorflow/core/grappler/utils.cc
+++ b/tensorflow/core/grappler/utils.cc
@@ -13,7 +13,10 @@ See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
+#include "tensorflow/core/grappler/utils.h"
+
#include <memory>
+#include <queue>
#include <vector>
#include "tensorflow/core/framework/attr_value.pb.h"
@@ -21,7 +24,6 @@ limitations under the License.
#include "tensorflow/core/framework/op.h"
#include "tensorflow/core/framework/op_def.pb.h"
#include "tensorflow/core/framework/types.h"
-#include "tensorflow/core/grappler/utils.h"
#include "tensorflow/core/lib/strings/numbers.h"
#include "tensorflow/core/lib/strings/scanner.h"
#include "tensorflow/core/lib/strings/strcat.h"
@@ -354,13 +356,51 @@ void DedupControlInputs(NodeDef* node) {
}
namespace {
+
+template <typename UniqueContainer>
+void EraseNodesFromGraphImpl(const UniqueContainer& nodes_to_delete,
+ GraphDef* graph) {
+ static_assert(std::is_same<typename UniqueContainer::value_type, int>::value,
+ "Need to pass container of ints");
+
+ int last = graph->node_size() - 1;
+ for (auto it = nodes_to_delete.rbegin(); it != nodes_to_delete.rend(); ++it) {
+ const int index = *it;
+ graph->mutable_node()->SwapElements(index, last);
+ last--;
+ }
+ graph->mutable_node()->DeleteSubrange(last + 1, nodes_to_delete.size());
+}
+
template <typename T>
inline void STLSortAndRemoveDuplicates(T* v) {
std::sort(v->begin(), v->end());
v->erase(std::unique(v->begin(), v->end()), v->end());
}
+
} // namespace
+void EraseNodesFromGraph(const std::set<int>& nodes_to_delete,
+ GraphDef* graph) {
+ EraseNodesFromGraphImpl(nodes_to_delete, graph);
+}
+
+void EraseNodesFromGraph(std::vector<int>&& nodes_to_delete, GraphDef* graph) {
+ STLSortAndRemoveDuplicates(&nodes_to_delete);
+ EraseNodesFromGraphImpl(nodes_to_delete, graph);
+}
+
+void EraseNodesFromGraph(const std::set<string>& nodes_to_delete,
+ GraphDef* graph) {
+ std::vector<int> nodes_idx_to_delete;
+ nodes_idx_to_delete.reserve(nodes_to_delete.size());
+ for (int i = 0; i < graph->node_size(); ++i) {
+ if (nodes_to_delete.count(graph->node(i).name()))
+ nodes_idx_to_delete.push_back(i);
+ }
+ EraseNodesFromGraphImpl(nodes_idx_to_delete, graph);
+}
+
Status SimpleGraphView::Initialize(
const GraphDef& graph,
const std::vector<std::pair<const NodeDef*, const NodeDef*>>*
diff --git a/tensorflow/core/grappler/utils.h b/tensorflow/core/grappler/utils.h
index 1c6fef59ea..b297caa8d4 100644
--- a/tensorflow/core/grappler/utils.h
+++ b/tensorflow/core/grappler/utils.h
@@ -209,6 +209,13 @@ void PermuteNodesInPlace(GraphDef* graph, std::vector<int>* permutation,
Status SetTensorValue(DataType dtype, int value, Tensor* tensor);
+void EraseNodesFromGraph(const std::set<int>& nodes_to_delete, GraphDef* graph);
+
+void EraseNodesFromGraph(std::vector<int>&& nodes_to_delete, GraphDef* graph);
+
+void EraseNodesFromGraph(const std::set<string>& nodes_to_delete,
+ GraphDef* graph);
+
class SimpleGraphView {
public:
// Build a graph view for the specified graphdef.
@@ -237,6 +244,9 @@ class SimpleGraphView {
DCHECK(it != name_to_index_.end());
return it == name_to_index_.end() ? -1 : it->second;
}
+ inline const NodeDef& node(int node_idx) const {
+ return graph_->node(node_idx);
+ }
inline const string& node_name(int node_idx) const {
return index_to_name_[node_idx];
}
diff --git a/tensorflow/core/grappler/utils_test.cc b/tensorflow/core/grappler/utils_test.cc
index 49a1996d25..c6e035834c 100644
--- a/tensorflow/core/grappler/utils_test.cc
+++ b/tensorflow/core/grappler/utils_test.cc
@@ -16,7 +16,9 @@ limitations under the License.
#include "tensorflow/core/grappler/utils.h"
#include "tensorflow/cc/ops/standard_ops.h"
#include "tensorflow/core/framework/node_def.pb.h"
+#include "tensorflow/core/grappler/grappler_item.h"
#include "tensorflow/core/lib/core/status.h"
+#include "tensorflow/core/lib/core/status_test_util.h"
#include "tensorflow/core/lib/core/threadpool.h"
#include "tensorflow/core/platform/env.h"
#include "tensorflow/core/platform/notification.h"
@@ -333,7 +335,9 @@ TEST_F(UtilsTest, NumNonControlOutputs) {
EXPECT_EQ(1, NumNonControlDataOutputs(*add_node, node_map));
}
-TEST_F(UtilsTest, DeleteNodes) {}
+TEST_F(UtilsTest, DeleteNodes) {
+ // TODO(rmlarsen): write forgtten test.
+}
} // namespace
} // namespace grappler
diff --git a/tensorflow/core/kernels/BUILD b/tensorflow/core/kernels/BUILD
index 2cb54bd973..6126e8b7ba 100644
--- a/tensorflow/core/kernels/BUILD
+++ b/tensorflow/core/kernels/BUILD
@@ -124,6 +124,7 @@ tf_kernel_library(
":bounds_check",
":dense_update_functor",
":ops_util",
+ ":training_op_helpers",
":variable_ops",
"//tensorflow/core:framework",
"//tensorflow/core:lib",
@@ -2346,6 +2347,22 @@ tf_cuda_cc_test(
)
tf_cuda_cc_test(
+ name = "crop_and_resize_op_benchmark_test",
+ srcs = ["crop_and_resize_op_benchmark_test.cc"],
+ deps = [
+ ":image",
+ ":ops_testutil",
+ ":ops_util",
+ "//tensorflow/core:core_cpu",
+ "//tensorflow/core:framework",
+ "//tensorflow/core:protos_all_cc",
+ "//tensorflow/core:test",
+ "//tensorflow/core:test_main",
+ "//tensorflow/core:testlib",
+ ],
+)
+
+tf_cuda_cc_test(
name = "resize_benchmark_test",
srcs = ["resize_op_benchmark_test.cc"],
deps = [
@@ -5350,10 +5367,6 @@ cc_library(
srcs = if_android(["decode_image_op.cc"]),
copts = tf_copts(),
linkopts = ["-ldl"],
- tags = [
- "manual",
- "notap",
- ],
visibility = ["//visibility:public"],
deps = [
"//tensorflow/core:android_gif_internal",
diff --git a/tensorflow/core/kernels/batching_util/adaptive_shared_batch_scheduler.h b/tensorflow/core/kernels/batching_util/adaptive_shared_batch_scheduler.h
index b77c14d012..656b6ced6d 100644
--- a/tensorflow/core/kernels/batching_util/adaptive_shared_batch_scheduler.h
+++ b/tensorflow/core/kernels/batching_util/adaptive_shared_batch_scheduler.h
@@ -147,13 +147,21 @@ class AdaptiveSharedBatchScheduler
// Tracks processing latency and adjusts in_flight_batches_limit to minimize.
void CallbackWrapper(const internal::ASBSBatch<TaskType>* batch,
- BatchProcessor callback);
+ BatchProcessor callback, bool is_express);
// Schedules batch if in_flight_batches_limit_ is not met.
void MaybeScheduleNextBatch() EXCLUSIVE_LOCKS_REQUIRED(mu_);
+ // Schedules the earliest closed batch in batches_
+ // if batch_thread_pool_ has an idle thead.
+ // Batches scheduled this way are called express batches.
+ // Express batches are not limited by in_flight_batches_limit_, and
+ // their latencies will not affect in_flight_batches_limit_.
+ void MaybeScheduleClosedBatch() EXCLUSIVE_LOCKS_REQUIRED(mu_);
+
// Notifies scheduler of non-empty batch which is eligible for processing.
- void AddBatch(const internal::ASBSBatch<TaskType>* batch);
+ void AddBatch(const internal::ASBSBatch<TaskType>* batch,
+ bool also_schedule_closed_batch);
// Removes queue from scheduler.
void RemoveQueue(const internal::ASBSQueue<TaskType>* queue);
@@ -180,8 +188,10 @@ class AdaptiveSharedBatchScheduler
// results in an actual cap of 3 80% of the time, and 4 20% of the time.
double in_flight_batches_limit_ GUARDED_BY(mu_);
- // Number of batches currently being processed.
+ // Number of regular batches currently being processed.
int64 in_flight_batches_ GUARDED_BY(mu_) = 0;
+ // Number of express batches currently being processed.
+ int64 in_flight_express_batches_ GUARDED_BY(mu_) = 0;
// RNG engine and distribution.
std::default_random_engine rand_engine_;
@@ -363,10 +373,14 @@ Status AdaptiveSharedBatchScheduler<TaskType>::AddQueue(
template <typename TaskType>
void AdaptiveSharedBatchScheduler<TaskType>::AddBatch(
- const internal::ASBSBatch<TaskType>* batch) {
+ const internal::ASBSBatch<TaskType>* batch,
+ bool also_schedule_closed_batch) {
mutex_lock l(mu_);
batches_.push_back(batch);
MaybeScheduleNextBatch();
+ if (also_schedule_closed_batch) {
+ MaybeScheduleClosedBatch();
+ }
}
template <typename TaskType>
@@ -407,19 +421,45 @@ void AdaptiveSharedBatchScheduler<TaskType>::MaybeScheduleNextBatch() {
batch->queue()->ReleaseBatch(batch);
batch_thread_pool_->Schedule(
std::bind(&AdaptiveSharedBatchScheduler<TaskType>::CallbackWrapper, this,
- batch, queues_and_callbacks_[batch->queue()]));
+ batch, queues_and_callbacks_[batch->queue()], false));
in_flight_batches_++;
}
template <typename TaskType>
+void AdaptiveSharedBatchScheduler<TaskType>::MaybeScheduleClosedBatch() {
+ if (in_flight_batches_ + in_flight_express_batches_ >=
+ options_.num_batch_threads) {
+ return;
+ }
+ for (auto it = batches_.begin(); it != batches_.end(); it++) {
+ if ((*it)->IsClosed()) {
+ const internal::ASBSBatch<TaskType>* batch = *it;
+ batches_.erase(it);
+ batch->queue()->ReleaseBatch(batch);
+ batch_thread_pool_->Schedule(
+ std::bind(&AdaptiveSharedBatchScheduler<TaskType>::CallbackWrapper,
+ this, batch, queues_and_callbacks_[batch->queue()], true));
+ in_flight_express_batches_++;
+ return;
+ }
+ }
+}
+
+template <typename TaskType>
void AdaptiveSharedBatchScheduler<TaskType>::CallbackWrapper(
const internal::ASBSBatch<TaskType>* batch,
- AdaptiveSharedBatchScheduler<TaskType>::BatchProcessor callback) {
+ AdaptiveSharedBatchScheduler<TaskType>::BatchProcessor callback,
+ bool is_express) {
int64 start_time = batch->creation_time_micros();
callback(std::unique_ptr<Batch<TaskType>>(
const_cast<internal::ASBSBatch<TaskType>*>(batch)));
int64 end_time = GetEnv()->NowMicros();
mutex_lock l(mu_);
+ if (is_express) {
+ in_flight_express_batches_--;
+ MaybeScheduleClosedBatch();
+ return;
+ }
in_flight_batches_--;
batch_count_++;
batch_latency_sum_ += end_time - start_time;
@@ -496,6 +536,7 @@ Status ASBSQueue<TaskType>::Schedule(std::unique_ptr<TaskType>* task) {
" is larger than maximum batch size ",
options_.max_batch_size);
}
+ bool is_old_batch_closed = false;
{
mutex_lock l(mu_);
// Current batch is full, create another if allowed.
@@ -505,6 +546,7 @@ Status ASBSQueue<TaskType>::Schedule(std::unique_ptr<TaskType>* task) {
return errors::Unavailable("The batch scheduling queue is full");
}
current_batch_->Close();
+ is_old_batch_closed = true;
current_batch_ = nullptr;
}
if (!current_batch_) {
@@ -516,7 +558,8 @@ Status ASBSQueue<TaskType>::Schedule(std::unique_ptr<TaskType>* task) {
num_enqueued_tasks_++;
}
// AddBatch must be called outside of lock, since it may call ReleaseBatch.
- if (new_batch != nullptr) scheduler_->AddBatch(new_batch);
+ if (new_batch != nullptr)
+ scheduler_->AddBatch(new_batch, is_old_batch_closed);
return Status::OK();
}
diff --git a/tensorflow/core/kernels/cast_op.cc b/tensorflow/core/kernels/cast_op.cc
index b4c97df38b..0478c93280 100644
--- a/tensorflow/core/kernels/cast_op.cc
+++ b/tensorflow/core/kernels/cast_op.cc
@@ -59,6 +59,8 @@ CastOpBase::CastOpBase(OpKernelConstruction* ctx) : OpKernel(ctx) {
OP_REQUIRES_OK(ctx, ctx->GetAttr("DstT", &external_dst_dtype_));
+ OP_REQUIRES_OK(ctx, ctx->GetAttr("Truncate", &use_truncation_));
+
// Quantized data types use the same underlying format as their non quantized
// version so we use the non quantized implementation for casting.
if (external_dst_dtype_ == DT_QUINT8) {
@@ -100,7 +102,7 @@ void CastOpBase::Compute(OpKernelContext* ctx) {
Tensor* out = nullptr;
OP_REQUIRES_OK(ctx, ctx->allocate_output(0, in.shape(), &out));
out->set_dtype(dst_dtype_);
- work_(ctx, in, out);
+ work_(ctx, in, out, use_truncation_);
out->set_dtype(external_dst_dtype_);
}
}
diff --git a/tensorflow/core/kernels/cast_op.h b/tensorflow/core/kernels/cast_op.h
index aae1e7ff19..527ab528c9 100644
--- a/tensorflow/core/kernels/cast_op.h
+++ b/tensorflow/core/kernels/cast_op.h
@@ -24,8 +24,71 @@ limitations under the License.
#include "tensorflow/core/platform/byte_order.h"
#include "tensorflow/core/platform/types.h"
+// Note that the GPU cast functor templates need to be instantiated unlike the
+// CPU ones, and hence their specializations are different than that for CPUs.
+#ifdef SPECIALIZE_FOR_GPUS
+#define SPECIALIZE_CAST(DEVICE, OUT_TYPE, IN_OUT) \
+ template <typename Device> \
+ struct CastFunctor<Device, OUT_TYPE, IN_OUT> { \
+ void operator()(const Device& d, \
+ typename TTypes<OUT_TYPE>::Flat out_tensor, \
+ typename TTypes<IN_OUT>::ConstFlat in_tensor, \
+ bool truncate = false) { \
+ if (truncate) { \
+ out_tensor.device(d) = \
+ in_tensor.unaryExpr(LSBZeroSetter<IN_OUT, OUT_TYPE>()) \
+ .template cast<OUT_TYPE>(); \
+ } else { \
+ out_tensor.device(d) = in_tensor.template cast<OUT_TYPE>(); \
+ } \
+ } \
+ }; \
+ template struct CastFunctor<DEVICE, OUT_TYPE, IN_OUT>;
+#else
+#define SPECIALIZE_CAST(DEVICE, OUT_TYPE, IN_OUT) \
+ template <> \
+ struct CastFunctor<DEVICE, OUT_TYPE, IN_OUT> { \
+ void operator()(const DEVICE& d, \
+ typename TTypes<OUT_TYPE>::Flat out_tensor, \
+ typename TTypes<IN_OUT>::ConstFlat in_tensor, \
+ bool truncate = false) { \
+ if (truncate) { \
+ out_tensor.device(d) = \
+ in_tensor.unaryExpr(LSBZeroSetter<IN_OUT, OUT_TYPE>()) \
+ .template cast<OUT_TYPE>(); \
+ } else { \
+ out_tensor.device(d) = in_tensor.template cast<OUT_TYPE>(); \
+ } \
+ } \
+ };
+#endif
+
+#define CAST_FUNCTORS(devname) \
+ SPECIALIZE_CAST(devname, float, double) \
+ SPECIALIZE_CAST(devname, float, std::complex<double>) \
+ SPECIALIZE_CAST(devname, std::complex<float>, std::complex<double>) \
+ SPECIALIZE_CAST(devname, std::complex<float>, double) \
+ SPECIALIZE_CAST(devname, Eigen::half, double) \
+ SPECIALIZE_CAST(devname, Eigen::half, float) \
+ SPECIALIZE_CAST(devname, Eigen::half, std::complex<double>) \
+ SPECIALIZE_CAST(devname, Eigen::half, std::complex<float>) \
+ SPECIALIZE_CAST(devname, bfloat16, float) \
+ template <typename OUT_TYPE, typename IN_OUT> \
+ struct CastFunctor<devname, OUT_TYPE, IN_OUT> { \
+ void operator()(const devname& d, \
+ typename TTypes<OUT_TYPE>::Flat out_tensor, \
+ typename TTypes<IN_OUT>::ConstFlat in_tensor, \
+ bool truncate = false) { \
+ out_tensor.device(d) = in_tensor.template cast<OUT_TYPE>(); \
+ } \
+ };
+
namespace tensorflow {
+typedef std::function<void(OpKernelContext*, const Tensor&, Tensor*,
+ bool trunc)>
+ CastFunctorType;
+
// Common base class of Cast kernels
class CastOpBase : public OpKernel {
public:
@@ -38,8 +101,8 @@ class CastOpBase : public OpKernel {
DataType dst_dtype_;
DataType external_src_dtype_;
DataType external_dst_dtype_;
- std::function<void(OpKernelContext*, const Tensor&, Tensor*)> work_ = nullptr;
-
+ bool use_truncation_;
+ CastFunctorType work_ = nullptr;
Status Unimplemented();
TF_DISALLOW_COPY_AND_ASSIGN(CastOpBase);
@@ -56,6 +119,23 @@ class CpuCastOp : public CastOpBase {
namespace functor {
+template <typename I>
+constexpr int MantissaWidth() {
+ return std::numeric_limits<I>::digits;
+}
+
+template <>
+constexpr int MantissaWidth<Eigen::half>() {
+ // Remember, there's 1 hidden bit
+ return 10 + 1;
+}
+
+template <>
+constexpr int MantissaWidth<bfloat16>() {
+ // Remember, there's 1 hidden bit
+ return 7 + 1;
+}
+
template <typename Device, typename Tout, typename Tin>
void Cast(const Device& d, typename TTypes<Tout>::Flat o,
typename TTypes<Tin>::ConstFlat i) {
@@ -65,7 +145,85 @@ void Cast(const Device& d, typename TTypes<Tout>::Flat o,
template <typename Device, typename Tout, typename Tin>
struct CastFunctor {
void operator()(const Device& d, typename TTypes<Tout>::Flat o,
- typename TTypes<Tin>::ConstFlat i);
+ typename TTypes<Tin>::ConstFlat i, bool truncate = false);
+};
+
+// Only enable LSBZeroSetterHelper for 64 and 32 bit input data types.
+// Specialize for others if needed in future.
+template <typename I>
+typename std::enable_if<sizeof(I) == 8, void>::type EIGEN_DEVICE_FUNC
+ EIGEN_STRONG_INLINE static LSBZeroSetterHelper(I& t, int n) {
+ // Only zero the bits for non-NaNs.
+ // For NaNs, let the non-truncation version handle it.
+ if (!std::isnan(t)) {
+ uint64_t* p = reinterpret_cast<uint64_t*>(&t);
+ *p &= (0xFFFFFFFFFFFFFFFF << n);
+ }
+}
+
+template <typename I>
+typename std::enable_if<sizeof(I) == 4, void>::type EIGEN_DEVICE_FUNC
+ EIGEN_STRONG_INLINE static LSBZeroSetterHelper(I& t, int n) {
+ // Only zero the bits for non-NaNs.
+ // For NaNs, let the non-truncation version handle it.
+ if (!std::isnan(t)) {
+ uint32_t* p = reinterpret_cast<uint32_t*>(&t);
+ *p &= (0xFFFFFFFF << n);
+ }
+}
+
+// Set n least significant bits to 0
+template <typename I, typename O>
+struct LSBZeroSetter {
+ EIGEN_EMPTY_STRUCT_CTOR(LSBZeroSetter)
+
+ EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE const I operator()(const I& a) const {
+ constexpr int bits = MantissaWidth<I>() - MantissaWidth<O>();
+ static_assert(
+ bits > 0,
+ "The output type must have fewer mantissa bits than the input type\n");
+ I t = a;
+ LSBZeroSetterHelper(t, bits);
+ return t;
+ }
+};
+
+template <typename I, typename O>
+struct LSBZeroSetter<std::complex<I>, std::complex<O>> {
+ EIGEN_EMPTY_STRUCT_CTOR(LSBZeroSetter)
+
+ EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE const std::complex<I> operator()(
+ const std::complex<I>& a) const {
+ constexpr int bits = MantissaWidth<I>() - MantissaWidth<O>();
+ static_assert(
+ bits > 0,
+ "The output type must have fewer mantissa bits than the input type\n");
+ I re = std::real(a);
+ I img = std::imag(a);
+ LSBZeroSetterHelper(re, bits);
+ LSBZeroSetterHelper(img, bits);
+ std::complex<I> toReturn(re, img);
+ return toReturn;
+ }
+};
+
+template <typename I, typename O>
+struct LSBZeroSetter<std::complex<I>, O> {
+ EIGEN_EMPTY_STRUCT_CTOR(LSBZeroSetter)
+ // Sets the 16 LSBits of the float to 0
+ EIGEN_DEVICE_FUNC EIGEN_STRONG_INLINE const std::complex<I> operator()(
+ const std::complex<I>& a) const {
+ constexpr int bits = MantissaWidth<I>() - MantissaWidth<O>();
+ static_assert(
+ bits > 0,
+ "The output type must have fewer mantissa bits than the input type\n");
+ I re = std::real(a);
+ I img = std::imag(a);
+ LSBZeroSetterHelper(re, bits);
+ LSBZeroSetterHelper(img, bits);
+ std::complex<I> toReturn(re, img);
+ return toReturn;
+ }
};
} // end namespace functor
diff --git a/tensorflow/core/kernels/cast_op_gpu.cu.cc b/tensorflow/core/kernels/cast_op_gpu.cu.cc
index 607e7f5efd..036996fca2 100644
--- a/tensorflow/core/kernels/cast_op_gpu.cu.cc
+++ b/tensorflow/core/kernels/cast_op_gpu.cu.cc
@@ -18,22 +18,19 @@ limitations under the License.
#define EIGEN_USE_GPU
#include "tensorflow/core/framework/bfloat16.h"
+#define SPECIALIZE_FOR_GPUS
#include "tensorflow/core/kernels/cast_op.h"
+#undef SPECIALIZE_FOR_GPUS
namespace tensorflow {
namespace functor {
typedef Eigen::GpuDevice GPUDevice;
-template <typename O, typename I>
-struct CastFunctor<GPUDevice, O, I> {
- void operator()(const GPUDevice& d, typename TTypes<O>::Flat o,
- typename TTypes<I>::ConstFlat i) {
- Cast<GPUDevice, O, I>(d, o, i);
- }
-};
+CAST_FUNCTORS(GPUDevice);
#define DEFINE(O, I) template struct CastFunctor<GPUDevice, O, I>
+
#define DEFINE_ALL_FROM(in_type) \
DEFINE(in_type, bool); \
DEFINE(in_type, uint8); \
@@ -59,14 +56,43 @@ DEFINE_ALL_FROM(int8);
DEFINE_ALL_FROM(int16);
DEFINE_ALL_FROM(int32);
DEFINE_ALL_FROM(int64);
-DEFINE_ALL_FROM(Eigen::half);
-DEFINE_ALL_FROM(float);
DEFINE_ALL_FROM(double);
-DEFINE_ALL_FROM(std::complex<float>);
DEFINE_ALL_FROM(std::complex<double>);
-DEFINE(bfloat16, float);
DEFINE(float, bfloat16);
+#define DEFINE_ALL_TO_FLOAT(out_type) \
+ DEFINE(out_type, bool); \
+ DEFINE(out_type, uint8); \
+ DEFINE(out_type, uint16); \
+ DEFINE(out_type, uint32); \
+ DEFINE(out_type, uint64); \
+ DEFINE(out_type, int8); \
+ DEFINE(out_type, int16); \
+ DEFINE(out_type, int32); \
+ DEFINE(out_type, int64); \
+ DEFINE(out_type, Eigen::half); \
+ DEFINE(out_type, float); \
+ DEFINE(out_type, std::complex<float>)
+
+#define DEFINE_ALL_TO_HALF(out_type) \
+ DEFINE(out_type, bool); \
+ DEFINE(out_type, uint8); \
+ DEFINE(out_type, uint16); \
+ DEFINE(out_type, uint32); \
+ DEFINE(out_type, uint64); \
+ DEFINE(out_type, int8); \
+ DEFINE(out_type, int16); \
+ DEFINE(out_type, int32); \
+ DEFINE(out_type, int64); \
+ DEFINE(out_type, Eigen::half)
+
+DEFINE_ALL_TO_HALF(Eigen::half);
+DEFINE_ALL_TO_HALF(bfloat16);
+DEFINE_ALL_TO_FLOAT(float);
+DEFINE_ALL_TO_FLOAT(std::complex<float>);
+
+#undef DEFINE_ALL_TO_FLOAT
+#undef DEFINE_ALL_TO_HALF
#undef DEFINE_ALL_FROM
#undef DEFINE
diff --git a/tensorflow/core/kernels/cast_op_impl.h b/tensorflow/core/kernels/cast_op_impl.h
index fe821b25df..b899bac681 100644
--- a/tensorflow/core/kernels/cast_op_impl.h
+++ b/tensorflow/core/kernels/cast_op_impl.h
@@ -25,22 +25,10 @@ namespace tensorflow {
namespace functor {
-template <typename O, typename I>
-struct CastFunctor<Eigen::ThreadPoolDevice, O, I> {
- void operator()(const Eigen::ThreadPoolDevice& d, typename TTypes<O>::Flat o,
- typename TTypes<I>::ConstFlat i) {
- o.device(d) = i.template cast<O>();
- }
-};
+CAST_FUNCTORS(Eigen::ThreadPoolDevice);
#ifdef TENSORFLOW_USE_SYCL
-template <typename O, typename I>
-struct CastFunctor<Eigen::SyclDevice, O, I> {
- void operator()(const Eigen::SyclDevice& d, typename TTypes<O>::Flat o,
- typename TTypes<I>::ConstFlat i) {
- o.device(d) = i.template cast<O>();
- }
-};
+CAST_FUNCTORS(Eigen::SyclDevice);
#endif // TENSORFLOW_USE_SYCL
} // namespace functor
@@ -68,139 +56,103 @@ struct CastFunctor<Eigen::SyclDevice, O, I> {
CURRY_TYPES3_NO_BF16(FN, arg0, arg1) \
FN(arg0, arg1, bfloat16);
-#define CAST_CASE(DEVICE, IN, OUT) \
- if (DataTypeToEnum<OUT>::value == dst_dtype) { \
- return [](OpKernelContext* ctx, const Tensor& inp, Tensor* out) { \
- functor::CastFunctor<DEVICE, OUT, IN> func; \
- func(ctx->eigen_device<DEVICE>(), out->flat<OUT>(), inp.flat<IN>()); \
- }; \
+#define CAST_CASE(DEVICE, IN, OUT) \
+ if (DataTypeToEnum<OUT>::value == dst_dtype) { \
+ return [](OpKernelContext* ctx, const Tensor& inp, Tensor* out, \
+ bool truncate) { \
+ functor::CastFunctor<DEVICE, OUT, IN> func; \
+ func(ctx->eigen_device<DEVICE>(), out->flat<OUT>(), inp.flat<IN>(), \
+ truncate); \
+ }; \
}
// The functions below are implemented in the cast_op_impl_*.cc files.
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromBool(DataType dst_dtype);
+CastFunctorType GetCpuCastFromBool(DataType dst_dtype);
+
+CastFunctorType GetCpuCastFromUint8(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromUint8(DataType dst_dtype);
+CastFunctorType GetCpuCastFromUint16(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromUint16(DataType dst_dtype);
+CastFunctorType GetCpuCastFromInt8(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromUint32(DataType dst_dtype);
+CastFunctorType GetCpuCastFromUint32(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromUint64(DataType dst_dtype);
+CastFunctorType GetCpuCastFromUint64(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromInt8(DataType dst_dtype);
+CastFunctorType GetCpuCastFromInt8(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromInt16(DataType dst_dtype);
+CastFunctorType GetCpuCastFromInt16(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromInt32(DataType dst_dtype);
+CastFunctorType GetCpuCastFromInt32(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromInt64(DataType dst_dtype);
+CastFunctorType GetCpuCastFromInt64(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromHalf(DataType dst_dtype);
+CastFunctorType GetCpuCastFromHalf(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromFloat(DataType dst_dtype);
+CastFunctorType GetCpuCastFromFloat(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromDouble(DataType dst_dtype);
+CastFunctorType GetCpuCastFromDouble(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromComplex64(DataType dst_dtype);
+CastFunctorType GetCpuCastFromComplex64(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromComplex128(DataType dst_dtype);
+CastFunctorType GetCpuCastFromComplex128(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromBfloat(DataType dst_dtype);
+CastFunctorType GetCpuCastFromBfloat(DataType dst_dtype);
#if GOOGLE_CUDA
// Same, for GPU.
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromBool(DataType dst_dtype);
+CastFunctorType GetGpuCastFromBool(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromUint8(DataType dst_dtype);
+CastFunctorType GetGpuCastFromUint8(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromUint16(DataType dst_dtype);
+CastFunctorType GetGpuCastFromUint16(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromUint32(DataType dst_dtype);
+CastFunctorType GetGpuCastFromInt8(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromUint64(DataType dst_dtype);
+CastFunctorType GetGpuCastFromUint32(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromInt8(DataType dst_dtype);
+CastFunctorType GetGpuCastFromUint64(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromInt16(DataType dst_dtype);
+CastFunctorType GetGpuCastFromInt16(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromInt32(DataType dst_dtype);
+CastFunctorType GetGpuCastFromInt32(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromInt64(DataType dst_dtype);
+CastFunctorType GetGpuCastFromInt64(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromHalf(DataType dst_dtype);
+CastFunctorType GetGpuCastFromHalf(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromFloat(DataType dst_dtype);
+CastFunctorType GetGpuCastFromFloat(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromDouble(DataType dst_dtype);
+CastFunctorType GetGpuCastFromDouble(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromComplex64(DataType dst_dtype);
+CastFunctorType GetGpuCastFromComplex64(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromComplex128(DataType dst_dtype);
+CastFunctorType GetGpuCastFromComplex128(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromBfloat(DataType dst_dtype);
+CastFunctorType GetGpuCastFromBfloat(DataType dst_dtype);
#endif // GOOGLE_CUDA
#ifdef TENSORFLOW_USE_SYCL
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromBool(DataType dst_dtype);
+CastFunctorType GetSyclCastFromBool(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromUint8(DataType dst_dtype);
+CastFunctorType GetSyclCastFromUint8(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromUint16(DataType dst_dtype);
+CastFunctorType GetSyclCastFromUint16(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromUint32(DataType dst_dtype);
+CastFunctorType GetSyclCastFromUint32(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromUint64(DataType dst_dtype);
+CastFunctorType GetSyclCastFromUint64(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromInt16(DataType dst_dtype);
+CastFunctorType GetSyclCastFromInt16(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromInt32(DataType dst_dtype);
+CastFunctorType GetSyclCastFromInt32(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromInt64(DataType dst_dtype);
+CastFunctorType GetSyclCastFromInt64(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromFloat(DataType dst_dtype);
+CastFunctorType GetSyclCastFromFloat(DataType dst_dtype);
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromDouble(DataType dst_dtype);
+CastFunctorType GetSyclCastFromDouble(DataType dst_dtype);
#endif // TENSORFLOW_USE_SYCL
} // namespace tensorflow
diff --git a/tensorflow/core/kernels/cast_op_impl_bfloat.cc b/tensorflow/core/kernels/cast_op_impl_bfloat.cc
index bfa7ba0d47..96aae15608 100644
--- a/tensorflow/core/kernels/cast_op_impl_bfloat.cc
+++ b/tensorflow/core/kernels/cast_op_impl_bfloat.cc
@@ -22,20 +22,19 @@ namespace tensorflow {
typedef Eigen::ThreadPoolDevice CPUDevice;
typedef Eigen::GpuDevice GPUDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromBfloat(DataType dst_dtype) {
+CastFunctorType GetCpuCastFromBfloat(DataType dst_dtype) {
CURRY_TYPES3(CAST_CASE, CPUDevice, bfloat16);
return nullptr;
}
#if GOOGLE_CUDA
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromBfloat(DataType dst_dtype) {
+CastFunctorType GetGpuCastFromBfloat(DataType dst_dtype) {
if (dst_dtype == DT_FLOAT) {
- return [](OpKernelContext* ctx, const Tensor& inp, Tensor* out) {
+ return [](OpKernelContext* ctx, const Tensor& inp, Tensor* out,
+ bool truncate) {
functor::CastFunctor<GPUDevice, float, bfloat16> func;
func(ctx->eigen_device<GPUDevice>(), out->flat<float>(),
- inp.flat<bfloat16>());
+ inp.flat<bfloat16>(), truncate);
};
}
return nullptr;
diff --git a/tensorflow/core/kernels/cast_op_impl_bool.cc b/tensorflow/core/kernels/cast_op_impl_bool.cc
index c5c7394b43..792d4781f2 100644
--- a/tensorflow/core/kernels/cast_op_impl_bool.cc
+++ b/tensorflow/core/kernels/cast_op_impl_bool.cc
@@ -20,15 +20,13 @@ namespace tensorflow {
typedef Eigen::ThreadPoolDevice CPUDevice;
typedef Eigen::GpuDevice GPUDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromBool(DataType dst_dtype) {
+CastFunctorType GetCpuCastFromBool(DataType dst_dtype) {
CURRY_TYPES3(CAST_CASE, CPUDevice, bool);
return nullptr;
}
#if GOOGLE_CUDA
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromBool(DataType dst_dtype) {
+CastFunctorType GetGpuCastFromBool(DataType dst_dtype) {
CURRY_TYPES3_NO_BF16(CAST_CASE, GPUDevice, bool);
return nullptr;
}
@@ -36,8 +34,7 @@ GetGpuCastFromBool(DataType dst_dtype) {
#ifdef TENSORFLOW_USE_SYCL
typedef Eigen::SyclDevice SYCLDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromBool(DataType dst_dtype) {
+CastFunctorType GetSyclCastFromBool(DataType dst_dtype) {
CURRY_TYPES3_NO_HALF(CAST_CASE, SYCLDevice, bool);
return nullptr;
}
diff --git a/tensorflow/core/kernels/cast_op_impl_complex128.cc b/tensorflow/core/kernels/cast_op_impl_complex128.cc
index 52899d58cd..9a184e5954 100644
--- a/tensorflow/core/kernels/cast_op_impl_complex128.cc
+++ b/tensorflow/core/kernels/cast_op_impl_complex128.cc
@@ -20,15 +20,13 @@ namespace tensorflow {
typedef Eigen::ThreadPoolDevice CPUDevice;
typedef Eigen::GpuDevice GPUDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromComplex128(DataType dst_dtype) {
+CastFunctorType GetCpuCastFromComplex128(DataType dst_dtype) {
CURRY_TYPES3(CAST_CASE, CPUDevice, std::complex<double>);
return nullptr;
}
#if GOOGLE_CUDA
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromComplex128(DataType dst_dtype) {
+CastFunctorType GetGpuCastFromComplex128(DataType dst_dtype) {
CURRY_TYPES3_NO_BF16(CAST_CASE, GPUDevice, std::complex<double>);
return nullptr;
}
diff --git a/tensorflow/core/kernels/cast_op_impl_complex64.cc b/tensorflow/core/kernels/cast_op_impl_complex64.cc
index 617bda53d5..77bc620b46 100644
--- a/tensorflow/core/kernels/cast_op_impl_complex64.cc
+++ b/tensorflow/core/kernels/cast_op_impl_complex64.cc
@@ -20,15 +20,13 @@ namespace tensorflow {
typedef Eigen::ThreadPoolDevice CPUDevice;
typedef Eigen::GpuDevice GPUDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromComplex64(DataType dst_dtype) {
+CastFunctorType GetCpuCastFromComplex64(DataType dst_dtype) {
CURRY_TYPES3(CAST_CASE, CPUDevice, std::complex<float>);
return nullptr;
}
#if GOOGLE_CUDA
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromComplex64(DataType dst_dtype) {
+CastFunctorType GetGpuCastFromComplex64(DataType dst_dtype) {
CURRY_TYPES3_NO_BF16(CAST_CASE, GPUDevice, std::complex<float>);
return nullptr;
}
diff --git a/tensorflow/core/kernels/cast_op_impl_double.cc b/tensorflow/core/kernels/cast_op_impl_double.cc
index 7dc485ddad..ff9056897f 100644
--- a/tensorflow/core/kernels/cast_op_impl_double.cc
+++ b/tensorflow/core/kernels/cast_op_impl_double.cc
@@ -20,15 +20,13 @@ namespace tensorflow {
typedef Eigen::ThreadPoolDevice CPUDevice;
typedef Eigen::GpuDevice GPUDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromDouble(DataType dst_dtype) {
+CastFunctorType GetCpuCastFromDouble(DataType dst_dtype) {
CURRY_TYPES3(CAST_CASE, CPUDevice, double);
return nullptr;
}
#if GOOGLE_CUDA
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromDouble(DataType dst_dtype) {
+CastFunctorType GetGpuCastFromDouble(DataType dst_dtype) {
CURRY_TYPES3_NO_BF16(CAST_CASE, GPUDevice, double);
return nullptr;
}
@@ -36,8 +34,7 @@ GetGpuCastFromDouble(DataType dst_dtype) {
#ifdef TENSORFLOW_USE_SYCL
typedef Eigen::SyclDevice SYCLDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromDouble(DataType dst_dtype) {
+CastFunctorType GetSyclCastFromDouble(DataType dst_dtype) {
CURRY_TYPES3_NO_HALF(CAST_CASE, SYCLDevice, double);
return nullptr;
}
diff --git a/tensorflow/core/kernels/cast_op_impl_float.cc b/tensorflow/core/kernels/cast_op_impl_float.cc
index 1c933914fd..f1e8f0e37b 100644
--- a/tensorflow/core/kernels/cast_op_impl_float.cc
+++ b/tensorflow/core/kernels/cast_op_impl_float.cc
@@ -22,15 +22,13 @@ namespace tensorflow {
typedef Eigen::ThreadPoolDevice CPUDevice;
typedef Eigen::GpuDevice GPUDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromFloat(DataType dst_dtype) {
+CastFunctorType GetCpuCastFromFloat(DataType dst_dtype) {
CURRY_TYPES3(CAST_CASE, CPUDevice, float);
return nullptr;
}
#if GOOGLE_CUDA
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromFloat(DataType dst_dtype) {
+CastFunctorType GetGpuCastFromFloat(DataType dst_dtype) {
CURRY_TYPES3(CAST_CASE, GPUDevice, float);
return nullptr;
}
@@ -38,8 +36,7 @@ GetGpuCastFromFloat(DataType dst_dtype) {
#ifdef TENSORFLOW_USE_SYCL
typedef Eigen::SyclDevice SYCLDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromFloat(DataType dst_dtype) {
+CastFunctorType GetSyclCastFromFloat(DataType dst_dtype) {
CURRY_TYPES3_NO_HALF(CAST_CASE, SYCLDevice, float);
return nullptr;
}
diff --git a/tensorflow/core/kernels/cast_op_impl_half.cc b/tensorflow/core/kernels/cast_op_impl_half.cc
index ef4b94e326..5da3a01352 100644
--- a/tensorflow/core/kernels/cast_op_impl_half.cc
+++ b/tensorflow/core/kernels/cast_op_impl_half.cc
@@ -20,15 +20,13 @@ namespace tensorflow {
typedef Eigen::ThreadPoolDevice CPUDevice;
typedef Eigen::GpuDevice GPUDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromHalf(DataType dst_dtype) {
+CastFunctorType GetCpuCastFromHalf(DataType dst_dtype) {
CURRY_TYPES3(CAST_CASE, CPUDevice, Eigen::half);
return nullptr;
}
#if GOOGLE_CUDA
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromHalf(DataType dst_dtype) {
+CastFunctorType GetGpuCastFromHalf(DataType dst_dtype) {
CURRY_TYPES3_NO_BF16(CAST_CASE, GPUDevice, Eigen::half);
return nullptr;
}
diff --git a/tensorflow/core/kernels/cast_op_impl_int16.cc b/tensorflow/core/kernels/cast_op_impl_int16.cc
index 59360f7445..440ee88fb5 100644
--- a/tensorflow/core/kernels/cast_op_impl_int16.cc
+++ b/tensorflow/core/kernels/cast_op_impl_int16.cc
@@ -20,15 +20,13 @@ namespace tensorflow {
typedef Eigen::ThreadPoolDevice CPUDevice;
typedef Eigen::GpuDevice GPUDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromInt16(DataType dst_dtype) {
+CastFunctorType GetCpuCastFromInt16(DataType dst_dtype) {
CURRY_TYPES3(CAST_CASE, CPUDevice, int16);
return nullptr;
}
#if GOOGLE_CUDA
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromInt16(DataType dst_dtype) {
+CastFunctorType GetGpuCastFromInt16(DataType dst_dtype) {
CURRY_TYPES3_NO_BF16(CAST_CASE, GPUDevice, int16);
return nullptr;
}
@@ -36,8 +34,7 @@ GetGpuCastFromInt16(DataType dst_dtype) {
#ifdef TENSORFLOW_USE_SYCL
typedef Eigen::SyclDevice SYCLDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromInt16(DataType dst_dtype) {
+CastFunctorType GetSyclCastFromInt16(DataType dst_dtype) {
CURRY_TYPES3_NO_HALF(CAST_CASE, SYCLDevice, int16);
return nullptr;
}
diff --git a/tensorflow/core/kernels/cast_op_impl_int32.cc b/tensorflow/core/kernels/cast_op_impl_int32.cc
index a867392fde..4b3e7efddc 100644
--- a/tensorflow/core/kernels/cast_op_impl_int32.cc
+++ b/tensorflow/core/kernels/cast_op_impl_int32.cc
@@ -20,15 +20,13 @@ namespace tensorflow {
typedef Eigen::ThreadPoolDevice CPUDevice;
typedef Eigen::GpuDevice GPUDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromInt32(DataType dst_dtype) {
+CastFunctorType GetCpuCastFromInt32(DataType dst_dtype) {
CURRY_TYPES3(CAST_CASE, CPUDevice, int32);
return nullptr;
}
#if GOOGLE_CUDA
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromInt32(DataType dst_dtype) {
+CastFunctorType GetGpuCastFromInt32(DataType dst_dtype) {
CURRY_TYPES3_NO_BF16(CAST_CASE, GPUDevice, int32);
return nullptr;
}
@@ -36,8 +34,7 @@ GetGpuCastFromInt32(DataType dst_dtype) {
#ifdef TENSORFLOW_USE_SYCL
typedef Eigen::SyclDevice SYCLDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromInt32(DataType dst_dtype) {
+CastFunctorType GetSyclCastFromInt32(DataType dst_dtype) {
CURRY_TYPES3_NO_HALF(CAST_CASE, SYCLDevice, int32);
return nullptr;
}
diff --git a/tensorflow/core/kernels/cast_op_impl_int64.cc b/tensorflow/core/kernels/cast_op_impl_int64.cc
index 467a8f6c89..0f711aa560 100644
--- a/tensorflow/core/kernels/cast_op_impl_int64.cc
+++ b/tensorflow/core/kernels/cast_op_impl_int64.cc
@@ -20,15 +20,13 @@ namespace tensorflow {
typedef Eigen::ThreadPoolDevice CPUDevice;
typedef Eigen::GpuDevice GPUDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromInt64(DataType dst_dtype) {
+CastFunctorType GetCpuCastFromInt64(DataType dst_dtype) {
CURRY_TYPES3(CAST_CASE, CPUDevice, int64);
return nullptr;
}
#if GOOGLE_CUDA
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromInt64(DataType dst_dtype) {
+CastFunctorType GetGpuCastFromInt64(DataType dst_dtype) {
CURRY_TYPES3_NO_BF16(CAST_CASE, GPUDevice, int64);
return nullptr;
}
@@ -36,8 +34,7 @@ GetGpuCastFromInt64(DataType dst_dtype) {
#ifdef TENSORFLOW_USE_SYCL
typedef Eigen::SyclDevice SYCLDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromInt64(DataType dst_dtype) {
+CastFunctorType GetSyclCastFromInt64(DataType dst_dtype) {
CURRY_TYPES3_NO_HALF(CAST_CASE, SYCLDevice, int64);
return nullptr;
}
diff --git a/tensorflow/core/kernels/cast_op_impl_int8.cc b/tensorflow/core/kernels/cast_op_impl_int8.cc
index 21002a4321..eac185d5a0 100644
--- a/tensorflow/core/kernels/cast_op_impl_int8.cc
+++ b/tensorflow/core/kernels/cast_op_impl_int8.cc
@@ -20,15 +20,13 @@ namespace tensorflow {
typedef Eigen::ThreadPoolDevice CPUDevice;
typedef Eigen::GpuDevice GPUDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromInt8(DataType dst_dtype) {
+CastFunctorType GetCpuCastFromInt8(DataType dst_dtype) {
CURRY_TYPES3(CAST_CASE, CPUDevice, int8);
return nullptr;
}
#if GOOGLE_CUDA
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromInt8(DataType dst_dtype) {
+CastFunctorType GetGpuCastFromInt8(DataType dst_dtype) {
CURRY_TYPES3_NO_BF16(CAST_CASE, GPUDevice, int8);
return nullptr;
}
@@ -36,8 +34,7 @@ GetGpuCastFromInt8(DataType dst_dtype) {
#ifdef TENSORFLOW_USE_SYCL
typedef Eigen::SyclDevice SYCLDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromInt8(DataType dst_dtype) {
+CastFunctorType GetSyclCastFromInt8(DataType dst_dtype) {
CURRY_TYPES3_NO_HALF(CAST_CASE, SYCLDevice, int8);
return nullptr;
}
diff --git a/tensorflow/core/kernels/cast_op_impl_uint16.cc b/tensorflow/core/kernels/cast_op_impl_uint16.cc
index cd829bae2a..3aebbdc1f3 100644
--- a/tensorflow/core/kernels/cast_op_impl_uint16.cc
+++ b/tensorflow/core/kernels/cast_op_impl_uint16.cc
@@ -20,15 +20,13 @@ namespace tensorflow {
typedef Eigen::ThreadPoolDevice CPUDevice;
typedef Eigen::GpuDevice GPUDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromUint16(DataType dst_dtype) {
+CastFunctorType GetCpuCastFromUint16(DataType dst_dtype) {
CURRY_TYPES3(CAST_CASE, CPUDevice, uint16);
return nullptr;
}
#if GOOGLE_CUDA
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromUint16(DataType dst_dtype) {
+CastFunctorType GetGpuCastFromUint16(DataType dst_dtype) {
CURRY_TYPES3_NO_BF16(CAST_CASE, GPUDevice, uint16);
return nullptr;
}
@@ -36,8 +34,7 @@ GetGpuCastFromUint16(DataType dst_dtype) {
#ifdef TENSORFLOW_USE_SYCL
typedef Eigen::SyclDevice SYCLDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromUint16(DataType dst_dtype) {
+CastFunctorType GetSyclCastFromUint16(DataType dst_dtype) {
CURRY_TYPES3_NO_HALF(CAST_CASE, SYCLDevice, uint16);
return nullptr;
}
diff --git a/tensorflow/core/kernels/cast_op_impl_uint32.cc b/tensorflow/core/kernels/cast_op_impl_uint32.cc
index d1a854d98b..86f5961bcc 100644
--- a/tensorflow/core/kernels/cast_op_impl_uint32.cc
+++ b/tensorflow/core/kernels/cast_op_impl_uint32.cc
@@ -20,15 +20,13 @@ namespace tensorflow {
typedef Eigen::ThreadPoolDevice CPUDevice;
typedef Eigen::GpuDevice GPUDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromUint32(DataType dst_dtype) {
+CastFunctorType GetCpuCastFromUint32(DataType dst_dtype) {
CURRY_TYPES3(CAST_CASE, CPUDevice, uint32);
return nullptr;
}
#if GOOGLE_CUDA
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromUint32(DataType dst_dtype) {
+CastFunctorType GetGpuCastFromUint32(DataType dst_dtype) {
CURRY_TYPES3_NO_BF16(CAST_CASE, GPUDevice, uint32);
return nullptr;
}
@@ -36,8 +34,7 @@ GetGpuCastFromUint32(DataType dst_dtype) {
#ifdef TENSORFLOW_USE_SYCL
typedef Eigen::SyclDevice SYCLDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromUint32(DataType dst_dtype) {
+CastFunctorType GetSyclCastFromUint32(DataType dst_dtype) {
CURRY_TYPES3_NO_HALF(CAST_CASE, SYCLDevice, uint32);
return nullptr;
}
diff --git a/tensorflow/core/kernels/cast_op_impl_uint64.cc b/tensorflow/core/kernels/cast_op_impl_uint64.cc
index 604e0424fc..6478c266ee 100644
--- a/tensorflow/core/kernels/cast_op_impl_uint64.cc
+++ b/tensorflow/core/kernels/cast_op_impl_uint64.cc
@@ -20,15 +20,13 @@ namespace tensorflow {
typedef Eigen::ThreadPoolDevice CPUDevice;
typedef Eigen::GpuDevice GPUDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromUint64(DataType dst_dtype) {
+CastFunctorType GetCpuCastFromUint64(DataType dst_dtype) {
CURRY_TYPES3(CAST_CASE, CPUDevice, uint64);
return nullptr;
}
#if GOOGLE_CUDA
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromUint64(DataType dst_dtype) {
+CastFunctorType GetGpuCastFromUint64(DataType dst_dtype) {
CURRY_TYPES3_NO_BF16(CAST_CASE, GPUDevice, uint64);
return nullptr;
}
@@ -36,8 +34,7 @@ GetGpuCastFromUint64(DataType dst_dtype) {
#ifdef TENSORFLOW_USE_SYCL
typedef Eigen::SyclDevice SYCLDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromUint64(DataType dst_dtype) {
+CastFunctorType GetSyclCastFromUint64(DataType dst_dtype) {
CURRY_TYPES3_NO_HALF(CAST_CASE, SYCLDevice, uint64);
return nullptr;
}
diff --git a/tensorflow/core/kernels/cast_op_impl_uint8.cc b/tensorflow/core/kernels/cast_op_impl_uint8.cc
index 2d1a6f3a4e..b22547a23e 100644
--- a/tensorflow/core/kernels/cast_op_impl_uint8.cc
+++ b/tensorflow/core/kernels/cast_op_impl_uint8.cc
@@ -20,15 +20,13 @@ namespace tensorflow {
typedef Eigen::ThreadPoolDevice CPUDevice;
typedef Eigen::GpuDevice GPUDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetCpuCastFromUint8(DataType dst_dtype) {
+CastFunctorType GetCpuCastFromUint8(DataType dst_dtype) {
CURRY_TYPES3(CAST_CASE, CPUDevice, uint8);
return nullptr;
}
#if GOOGLE_CUDA
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetGpuCastFromUint8(DataType dst_dtype) {
+CastFunctorType GetGpuCastFromUint8(DataType dst_dtype) {
CURRY_TYPES3_NO_BF16(CAST_CASE, GPUDevice, uint8);
return nullptr;
}
@@ -36,8 +34,7 @@ GetGpuCastFromUint8(DataType dst_dtype) {
#ifdef TENSORFLOW_USE_SYCL
typedef Eigen::SyclDevice SYCLDevice;
-std::function<void(OpKernelContext*, const Tensor&, Tensor*)>
-GetSyclCastFromUint8(DataType dst_dtype) {
+CastFunctorType GetSyclCastFromUint8(DataType dst_dtype) {
CURRY_TYPES3_NO_HALF(CAST_CASE, SYCLDevice, uint8);
return nullptr;
}
diff --git a/tensorflow/core/kernels/cast_op_test.cc b/tensorflow/core/kernels/cast_op_test.cc
index 9bbf7afb16..cb305de5e3 100644
--- a/tensorflow/core/kernels/cast_op_test.cc
+++ b/tensorflow/core/kernels/cast_op_test.cc
@@ -40,17 +40,27 @@ static Graph* Cast(int num) {
class CastOpTest : public OpsTestBase {
protected:
- void MakeOp(DataType src, DataType dst) {
- TF_EXPECT_OK(NodeDefBuilder("cast_op", "Cast")
- .Input(FakeInput(src))
- .Attr("SrcT", src)
- .Attr("DstT", dst)
- .Finalize(node_def()));
+ void MakeOp(DataType src, DataType dst, bool trunc = false) {
+ if (trunc) {
+ TF_EXPECT_OK(NodeDefBuilder("cast_op", "Cast")
+ .Input(FakeInput(src))
+ .Attr("SrcT", src)
+ .Attr("DstT", dst)
+ .Attr("Truncate", true)
+ .Finalize(node_def()));
+ } else {
+ TF_EXPECT_OK(NodeDefBuilder("cast_op", "Cast")
+ .Input(FakeInput(src))
+ .Attr("SrcT", src)
+ .Attr("DstT", dst)
+ .Finalize(node_def()));
+ }
+
TF_EXPECT_OK(InitOp());
}
template <typename INPUT, typename OUTPUT>
- void CheckCast() {
+ void CheckCast(bool trunc = false) {
DataType in_type = DataTypeToEnum<INPUT>::v();
DataType out_type = DataTypeToEnum<OUTPUT>::v();
MakeOp(in_type, out_type);
@@ -64,8 +74,9 @@ class CastOpTest : public OpsTestBase {
}
};
-#define TEST_CAST(in, out) \
- TEST_F(CastOpTest, TestCast##_##in##_##out) { CheckCast<in, out>(); }
+#define TEST_CAST(in, out) \
+ TEST_F(CastOpTest, TestCast##_##in##_##out) { CheckCast<in, out>(); } \
+ TEST_F(CastOpTest, TestCast2##_##in##_##out) { CheckCast<in, out>(true); }
#define TEST_ALL_CASTS_FROM(in) \
TEST_CAST(in, uint8); \
diff --git a/tensorflow/core/kernels/conv_ops_test.cc b/tensorflow/core/kernels/conv_ops_test.cc
index c281153795..1236f27051 100644
--- a/tensorflow/core/kernels/conv_ops_test.cc
+++ b/tensorflow/core/kernels/conv_ops_test.cc
@@ -229,7 +229,7 @@ class FusedResizePadConvOpTest : public OpsTestBase {
std::vector<Tensor> fused_tensors;
TF_ASSERT_OK(session->Run({}, {"fused_conv"}, {}, &fused_tensors));
- test::ExpectTensorNear<T>(unfused_tensors[0], fused_tensors[0], 1e-5);
+ test::ExpectClose(unfused_tensors[0], fused_tensors[0]);
}
template <typename T>
@@ -282,7 +282,7 @@ class FusedResizePadConvOpTest : public OpsTestBase {
std::vector<Tensor> fused_tensors;
TF_ASSERT_OK(session->Run({}, {"fused_conv"}, {}, &fused_tensors));
- test::ExpectTensorNear<T>(unfused_tensors[0], fused_tensors[0], 1e-5);
+ test::ExpectClose(unfused_tensors[0], fused_tensors[0]);
}
};
diff --git a/tensorflow/core/kernels/crop_and_resize_op_benchmark_test.cc b/tensorflow/core/kernels/crop_and_resize_op_benchmark_test.cc
new file mode 100644
index 0000000000..d7ca64bea0
--- /dev/null
+++ b/tensorflow/core/kernels/crop_and_resize_op_benchmark_test.cc
@@ -0,0 +1,72 @@
+/* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/core/common_runtime/kernel_benchmark_testlib.h"
+#include "tensorflow/core/framework/tensor.h"
+#include "tensorflow/core/graph/node_builder.h"
+#include "tensorflow/core/platform/test.h"
+#include "tensorflow/core/platform/test_benchmark.h"
+
+namespace tensorflow {
+
+static Graph* BM_CropAndResize(int batches, int width, int height, int depth,
+ int crop_height, int crop_width) {
+ Graph* g = new Graph(OpRegistry::Global());
+ Tensor in(DT_FLOAT, TensorShape({batches, height, width, depth}));
+ in.flat<float>().setRandom();
+ Tensor boxes(DT_FLOAT, TensorShape({batches, 4}));
+ auto boxes_tensor = boxes.matrix<float>();
+ Tensor box_ind(DT_INT32, TensorShape({batches}));
+ auto box_ind_flat = box_ind.flat<int32>();
+ for (int i = 0; i < batches; ++i) {
+ boxes_tensor(i, 0) = 0.2;
+ boxes_tensor(i, 1) = 0.2;
+ boxes_tensor(i, 2) = 0.8;
+ boxes_tensor(i, 3) = 0.7;
+ box_ind_flat(i) = i;
+ }
+ Tensor crop_size(DT_INT32, TensorShape({2}));
+ auto crop_size_flat = crop_size.flat<int32>();
+ crop_size_flat(0) = crop_height;
+ crop_size_flat(1) = crop_width;
+ Node* ret;
+ TF_CHECK_OK(NodeBuilder(g->NewName("n"), "CropAndResize")
+ .Input(test::graph::Constant(g, in))
+ .Input(test::graph::Constant(g, boxes))
+ .Input(test::graph::Constant(g, box_ind))
+ .Input(test::graph::Constant(g, crop_size))
+ .Finalize(g, &ret));
+ return g;
+}
+
+#define BM_CropAndResizeDev(DEVICE, B, W, H, D, CH, CW) \
+ static void BM_CropAndResize_##DEVICE##_##B##_##W##_##H##_##D##_##CH##_##CW( \
+ int iters) { \
+ testing::ItemsProcessed(iters* B* W* H* D); \
+ test::Benchmark(#DEVICE, BM_CropAndResize(B, W, H, D, CH, CW)).Run(iters); \
+ } \
+ BENCHMARK(BM_CropAndResize_##DEVICE##_##B##_##W##_##H##_##D##_##CH##_##CW);
+
+// Benchmark results using CPU:Intel Haswell with HyperThreading (6 cores)
+// Benchmark Time(ns) CPU(ns) Iterations
+// BM_CropAndResize_cpu_1_640_640_3_512_512 7078765 7173520 100 163.361M items/s
+// BM_CropAndResize_cpu_1_640_640_1_512_512 3801232 3914692 185 99.784M items/s
+// BM_CropAndResize_cpu_1_80_80_512_7_7 182470 241767 2941 1.372G items/s
+
+BM_CropAndResizeDev(cpu, 1, 640, 640, 3, 512, 512);
+BM_CropAndResizeDev(cpu, 1, 640, 640, 1, 512, 512);
+BM_CropAndResizeDev(cpu, 1, 80, 80, 512, 7, 7);
+
+} // namespace tensorflow
diff --git a/tensorflow/core/kernels/cwise_op_tan.cc b/tensorflow/core/kernels/cwise_op_tan.cc
index c1a25767d3..90762fb1b0 100644
--- a/tensorflow/core/kernels/cwise_op_tan.cc
+++ b/tensorflow/core/kernels/cwise_op_tan.cc
@@ -16,7 +16,8 @@ limitations under the License.
#include "tensorflow/core/kernels/cwise_ops_common.h"
namespace tensorflow {
-REGISTER2(UnaryOp, CPU, "Tan", functor::tan, float, double);
+REGISTER4(UnaryOp, CPU, "Tan", functor::tan, float, double, complex64,
+ complex128);
#if GOOGLE_CUDA
REGISTER2(UnaryOp, GPU, "Tan", functor::tan, float, double);
diff --git a/tensorflow/core/kernels/data/BUILD b/tensorflow/core/kernels/data/BUILD
index e04fa20414..90bc3da0cd 100644
--- a/tensorflow/core/kernels/data/BUILD
+++ b/tensorflow/core/kernels/data/BUILD
@@ -204,12 +204,28 @@ tf_kernel_library(
],
)
+cc_library(
+ name = "parallel_map_iterator",
+ srcs = ["parallel_map_iterator.cc"],
+ hdrs = ["parallel_map_iterator.h"],
+ deps = [
+ ":dataset",
+ "//tensorflow/core:core_cpu_internal",
+ "//tensorflow/core:dataset_ops_op_lib",
+ "//tensorflow/core:framework",
+ "//tensorflow/core:lib",
+ "//tensorflow/core:lib_internal",
+ "//tensorflow/core:protos_all_cc",
+ ],
+)
+
tf_kernel_library(
name = "parallel_map_dataset_op",
srcs = ["parallel_map_dataset_op.cc"],
deps = [
":captured_function",
":dataset",
+ ":parallel_map_iterator",
"//tensorflow/core:core_cpu_internal",
"//tensorflow/core:dataset_ops_op_lib",
"//tensorflow/core:framework",
diff --git a/tensorflow/core/kernels/data/parallel_map_dataset_op.cc b/tensorflow/core/kernels/data/parallel_map_dataset_op.cc
index 15f3dc3b1d..b736b33c2e 100644
--- a/tensorflow/core/kernels/data/parallel_map_dataset_op.cc
+++ b/tensorflow/core/kernels/data/parallel_map_dataset_op.cc
@@ -19,6 +19,7 @@ limitations under the License.
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/kernels/data/captured_function.h"
#include "tensorflow/core/kernels/data/dataset.h"
+#include "tensorflow/core/kernels/data/parallel_map_iterator.h"
#include "tensorflow/core/lib/core/error_codes.pb.h"
#include "tensorflow/core/lib/random/random.h"
@@ -87,8 +88,16 @@ class ParallelMapDatasetOp : public UnaryDatasetOpKernel {
std::unique_ptr<IteratorBase> MakeIteratorInternal(
const string& prefix) const override {
- return std::unique_ptr<IteratorBase>(
- new Iterator({this, strings::StrCat(prefix, "::ParallelMap")}));
+ auto map_func = [this](IteratorContext* ctx,
+ std::vector<Tensor> input_element,
+ std::vector<Tensor>* result, StatusCallback done) {
+ captured_func_->RunAsync(ctx, std::move(input_element), result,
+ std::move(done));
+ };
+
+ return NewParallelMapIterator(
+ {this, strings::StrCat(prefix, "::ParallelMap")}, input_,
+ std::move(map_func), num_parallel_calls_);
}
const DataTypeVector& output_dtypes() const override {
@@ -148,279 +157,6 @@ class ParallelMapDatasetOp : public UnaryDatasetOpKernel {
}
private:
- class Iterator : public DatasetIterator<Dataset> {
- public:
- explicit Iterator(const Params& params)
- : DatasetIterator<Dataset>(params) {}
-
- ~Iterator() override {
- // TODO(mrry): Replace this cancellation logic with a
- // CancellationManager. The syntax would be more heavyweight,
- // but it would be possible to thread a cancellation manager
- // through the IteratorContext to upstream,
- // potentially-blocking iterators, when we add these.
- mutex_lock l(mu_);
- // Cancel the runner thread.
- cancelled_ = true;
- cond_var_.notify_all();
- // Wait for all in-flight calls to complete.
- while (num_calls_ > 0) {
- cond_var_.wait(l);
- }
- }
-
- Status Initialize(IteratorContext* ctx) override {
- return dataset()->input_->MakeIterator(ctx, prefix(), &input_impl_);
- }
-
- Status GetNextInternal(IteratorContext* ctx,
- std::vector<Tensor>* out_tensors,
- bool* end_of_sequence) override {
- std::shared_ptr<InvocationResult> result;
- {
- mutex_lock l(mu_);
- EnsureRunnerThreadStarted(ctx);
- while (invocation_results_.empty()) {
- cond_var_.wait(l);
- }
- std::swap(result, invocation_results_.front());
- invocation_results_.pop_front();
- }
- cond_var_.notify_all();
- result->notification.WaitForNotification();
- return ProcessResult(result, out_tensors, end_of_sequence);
- }
-
- protected:
- Status SaveInternal(IteratorStateWriter* writer) override {
- mutex_lock l(mu_);
- // Wait for all in-flight calls to complete.
- while (num_calls_ > 0) {
- cond_var_.wait(l);
- }
- CHECK_EQ(num_calls_, 0);
- TF_RETURN_IF_ERROR(SaveParent(writer, input_impl_));
- TF_RETURN_IF_ERROR(writer->WriteScalar(
- full_name("invocation_results.size"), invocation_results_.size()));
- for (size_t i = 0; i < invocation_results_.size(); i++) {
- std::shared_ptr<InvocationResult> result = invocation_results_[i];
- TF_RETURN_IF_ERROR(WriteStatusLocked(writer, i, result->status));
- TF_RETURN_IF_ERROR(writer->WriteScalar(
- full_name(strings::StrCat("invocation_results[", i, "].size")),
- result->return_values.size()));
- for (size_t j = 0; j < result->return_values.size(); j++) {
- TF_RETURN_IF_ERROR(writer->WriteTensor(
- full_name(
- strings::StrCat("invocation_results[", i, "][", j, "]")),
- result->return_values[j]));
- }
- if (result->end_of_input) {
- TF_RETURN_IF_ERROR(writer->WriteScalar(
- full_name(strings::StrCat("invocation_results[", i,
- "].end_of_input")),
- ""));
- }
- }
- return Status::OK();
- }
-
- Status RestoreInternal(IteratorContext* ctx,
- IteratorStateReader* reader) override {
- mutex_lock l(mu_);
- TF_RETURN_IF_ERROR(RestoreParent(ctx, reader, input_impl_));
- int64 invocation_results_size;
- TF_RETURN_IF_ERROR(reader->ReadScalar(
- full_name("invocation_results.size"), &invocation_results_size));
- for (size_t i = 0; i < invocation_results_size; i++) {
- std::shared_ptr<InvocationResult> result(new InvocationResult());
- invocation_results_.push_back(result);
- TF_RETURN_IF_ERROR(ReadStatusLocked(reader, i, &result->status));
- size_t num_return_values;
- {
- int64 size;
- TF_RETURN_IF_ERROR(reader->ReadScalar(
- full_name(strings::StrCat("invocation_results[", i, "].size")),
- &size));
- num_return_values = static_cast<size_t>(size);
- if (num_return_values != size) {
- return errors::InvalidArgument(strings::StrCat(
- full_name(
- strings::StrCat("invocation_results[", i, "].size")),
- ": ", size, " is not a valid value of type size_t."));
- }
- }
- result->return_values.reserve(num_return_values);
- for (size_t j = 0; j < num_return_values; j++) {
- result->return_values.emplace_back();
- TF_RETURN_IF_ERROR(
- reader->ReadTensor(full_name(strings::StrCat(
- "invocation_results[", i, "][", j, "]")),
- &result->return_values.back()));
- }
- result->end_of_input = reader->Contains(full_name(
- strings::StrCat("invocation_results[", i, "].end_of_input")));
- result->notification.Notify();
- }
- return Status::OK();
- }
-
- private:
- struct InvocationResult {
- Notification notification;
- Status status;
- std::vector<Tensor> return_values;
- bool end_of_input;
- };
-
- void EnsureRunnerThreadStarted(IteratorContext* ctx)
- EXCLUSIVE_LOCKS_REQUIRED(mu_) {
- if (!runner_thread_) {
- std::shared_ptr<IteratorContext> ctx_copy(new IteratorContext(*ctx));
- runner_thread_.reset(ctx->env()->StartThread(
- {}, "runner_thread",
- std::bind(&Iterator::RunnerThread, this, ctx_copy)));
- }
- }
-
- void CallCompleted(const std::shared_ptr<InvocationResult>& result)
- LOCKS_EXCLUDED(mu_) {
- {
- mutex_lock l(mu_);
- num_calls_--;
- }
- result->notification.Notify();
- cond_var_.notify_all();
- }
-
- void CallFunction(const std::shared_ptr<IteratorContext>& ctx,
- const std::shared_ptr<InvocationResult>& result)
- LOCKS_EXCLUDED(mu_) {
- // Get the next input element.
- std::vector<Tensor> input_element;
- result->status = input_impl_->GetNext(ctx.get(), &input_element,
- &result->end_of_input);
- if (result->end_of_input || !result->status.ok()) {
- CallCompleted(result);
- return;
- }
-
- // Call `func_(input_element)`, store the result in
- // `result->return_values`, and notify `result->notification` to unblock
- // a consumer.
- auto done = [this, result](Status status) {
- result->status.Update(status);
- CallCompleted(result);
- };
- dataset()->captured_func_->RunAsync(ctx.get(), std::move(input_element),
- &result->return_values, done);
- }
-
- int64 MaxInvocationResults() { return dataset()->num_parallel_calls_; }
-
- Status ProcessResult(const std::shared_ptr<InvocationResult>& result,
- std::vector<Tensor>* out_tensors,
- bool* end_of_sequence) {
- if (!result->end_of_input && result->status.ok()) {
- *out_tensors = std::move(result->return_values);
- *end_of_sequence = false;
- return Status::OK();
- }
- if (errors::IsOutOfRange(result->status)) {
- // `f` may deliberately raise `errors::OutOfRange` to indicate that we
- // should terminate the iteration early.
- *end_of_sequence = true;
- return Status::OK();
- }
- *end_of_sequence = result->end_of_input;
- return result->status;
- }
-
- void RunnerThread(const std::shared_ptr<IteratorContext>& ctx) {
- std::vector<std::shared_ptr<InvocationResult>> new_calls;
- new_calls.reserve(dataset()->num_parallel_calls_);
- while (true) {
- {
- mutex_lock l(mu_);
- while (!cancelled_ &&
- (num_calls_ >= dataset()->num_parallel_calls_ ||
- invocation_results_.size() >= MaxInvocationResults())) {
- cond_var_.wait(l);
- }
- if (cancelled_) {
- return;
- }
- while (num_calls_ < dataset()->num_parallel_calls_ &&
- invocation_results_.size() < MaxInvocationResults()) {
- invocation_results_.emplace_back(new InvocationResult());
- new_calls.push_back(invocation_results_.back());
- num_calls_++;
- }
- }
- cond_var_.notify_all();
- for (const auto& call : new_calls) {
- CallFunction(ctx, call);
- }
- new_calls.clear();
- }
- }
-
- Status WriteStatusLocked(IteratorStateWriter* writer, size_t index,
- const Status& status)
- EXCLUSIVE_LOCKS_REQUIRED(mu_) {
- TF_RETURN_IF_ERROR(writer->WriteScalar(
- CodeKey(index), static_cast<int64>(status.code())));
- if (!status.ok()) {
- TF_RETURN_IF_ERROR(writer->WriteScalar(ErrorMessageKey(index),
- status.error_message()));
- }
- return Status::OK();
- }
-
- Status ReadStatusLocked(IteratorStateReader* reader, size_t index,
- Status* status) EXCLUSIVE_LOCKS_REQUIRED(mu_) {
- int64 code_int;
- TF_RETURN_IF_ERROR(reader->ReadScalar(CodeKey(index), &code_int));
- error::Code code = static_cast<error::Code>(code_int);
-
- if (code != error::Code::OK) {
- string error_message;
- TF_RETURN_IF_ERROR(
- reader->ReadScalar(ErrorMessageKey(index), &error_message));
- *status = Status(code, error_message);
- } else {
- *status = Status::OK();
- }
- return Status::OK();
- }
-
- string CodeKey(size_t index) {
- return full_name(
- strings::StrCat("invocation_results[", index, "].code"));
- }
-
- string ErrorMessageKey(size_t index) {
- return full_name(
- strings::StrCat("invocation_results[", index, "].error_message"));
- }
-
- // Used for coordination between the main thread and the runner thread.
- mutex mu_;
- // Used for coordination between the main thread and the runner thread. In
- // particular, the runner thread should only schedule new calls when the
- // number of in-flight calls is less than the user specified level of
- // parallelism and there are slots available in the `invocation_results_`
- // buffer.
- condition_variable cond_var_;
- // Counts the number of outstanding calls.
- int64 num_calls_ GUARDED_BY(mu_) = 0;
- std::unique_ptr<IteratorBase> input_impl_;
- // Buffer for storing the invocation results.
- std::deque<std::shared_ptr<InvocationResult>> invocation_results_
- GUARDED_BY(mu_);
- std::unique_ptr<Thread> runner_thread_ GUARDED_BY(mu_);
- bool cancelled_ GUARDED_BY(mu_) = false;
- };
-
const DatasetBase* const input_;
const NameAttrList func_;
const int32 num_parallel_calls_;
diff --git a/tensorflow/core/kernels/data/parallel_map_iterator.cc b/tensorflow/core/kernels/data/parallel_map_iterator.cc
new file mode 100644
index 0000000000..10549df25e
--- /dev/null
+++ b/tensorflow/core/kernels/data/parallel_map_iterator.cc
@@ -0,0 +1,318 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#include "tensorflow/core/kernels/data/parallel_map_iterator.h"
+
+#include <deque>
+#include <functional>
+#include <utility>
+#include <vector>
+
+namespace tensorflow {
+namespace {
+
+class ParallelMapIterator : public DatasetBaseIterator {
+ public:
+ explicit ParallelMapIterator(
+ const typename DatasetBaseIterator::BaseParams& params,
+ const DatasetBase* input_dataset, ParallelMapIteratorFunction map_func,
+ int32 num_parallel_calls)
+ : DatasetBaseIterator(params),
+ input_dataset_(input_dataset),
+ map_func_(std::move(map_func)),
+ num_parallel_calls_(num_parallel_calls) {}
+
+ ~ParallelMapIterator() override {
+ // TODO(mrry): Replace this cancellation logic with a
+ // CancellationManager. The syntax would be more heavyweight,
+ // but it would be possible to thread a cancellation manager
+ // through the IteratorContext to upstream,
+ // potentially-blocking iterators, when we add these.
+ mutex_lock l(mu_);
+ // Cancel the runner thread.
+ cancelled_ = true;
+ cond_var_.notify_all();
+ // Wait for all in-flight calls to complete.
+ while (num_calls_ > 0) {
+ cond_var_.wait(l);
+ }
+ }
+
+ Status Initialize(IteratorContext* ctx) override {
+ return input_dataset_->MakeIterator(ctx, prefix(), &input_impl_);
+ }
+
+ Status GetNextInternal(IteratorContext* ctx, std::vector<Tensor>* out_tensors,
+ bool* end_of_sequence) override {
+ std::shared_ptr<InvocationResult> result;
+ {
+ mutex_lock l(mu_);
+ EnsureRunnerThreadStarted(ctx);
+ while (invocation_results_.empty()) {
+ cond_var_.wait(l);
+ }
+ std::swap(result, invocation_results_.front());
+ invocation_results_.pop_front();
+ }
+ cond_var_.notify_all();
+ result->notification.WaitForNotification();
+ return ProcessResult(result, out_tensors, end_of_sequence);
+ }
+
+ protected:
+ Status SaveInternal(IteratorStateWriter* writer) override {
+ mutex_lock l(mu_);
+ // Wait for all in-flight calls to complete.
+ while (num_calls_ > 0) {
+ cond_var_.wait(l);
+ }
+ CHECK_EQ(num_calls_, 0);
+ TF_RETURN_IF_ERROR(SaveParent(writer, input_impl_));
+ TF_RETURN_IF_ERROR(
+ writer->WriteScalar(full_name("invocation_results.size"),
+ invocation_results_.size()));
+ for (size_t i = 0; i < invocation_results_.size(); i++) {
+ std::shared_ptr<InvocationResult> result = invocation_results_[i];
+ TF_RETURN_IF_ERROR(WriteStatusLocked(writer, i, result->status));
+ TF_RETURN_IF_ERROR(writer->WriteScalar(
+ full_name(strings::StrCat("invocation_results[", i, "].size")),
+ result->return_values.size()));
+ for (size_t j = 0; j < result->return_values.size(); j++) {
+ TF_RETURN_IF_ERROR(
+ writer->WriteTensor(full_name(strings::StrCat(
+ "invocation_results[", i, "][", j, "]")),
+ result->return_values[j]));
+ }
+ if (result->end_of_input) {
+ TF_RETURN_IF_ERROR(writer->WriteScalar(
+ full_name(
+ strings::StrCat("invocation_results[", i, "].end_of_input")),
+ ""));
+ }
+ }
+ return Status::OK();
+ }
+
+ Status RestoreInternal(IteratorContext* ctx,
+ IteratorStateReader* reader) override {
+ mutex_lock l(mu_);
+ TF_RETURN_IF_ERROR(RestoreParent(ctx, reader, input_impl_));
+ int64 invocation_results_size;
+ TF_RETURN_IF_ERROR(reader->ReadScalar(
+ full_name("invocation_results.size"), &invocation_results_size));
+ for (size_t i = 0; i < invocation_results_size; i++) {
+ std::shared_ptr<InvocationResult> result(new InvocationResult());
+ invocation_results_.push_back(result);
+ TF_RETURN_IF_ERROR(ReadStatusLocked(reader, i, &result->status));
+ size_t num_return_values;
+ {
+ int64 size;
+ TF_RETURN_IF_ERROR(
+ reader->ReadScalar(full_name(strings::StrCat(
+ "invocation_results[", i, "].size")),
+ &size));
+ num_return_values = static_cast<size_t>(size);
+ if (num_return_values != size) {
+ return errors::InvalidArgument(strings::StrCat(
+ full_name(
+ strings::StrCat("invocation_results[", i, "].size")),
+ ": ", size, " is not a valid value of type size_t."));
+ }
+ }
+ result->return_values.reserve(num_return_values);
+ for (size_t j = 0; j < num_return_values; j++) {
+ result->return_values.emplace_back();
+ TF_RETURN_IF_ERROR(
+ reader->ReadTensor(full_name(strings::StrCat(
+ "invocation_results[", i, "][", j, "]")),
+ &result->return_values.back()));
+ }
+ result->end_of_input = reader->Contains(full_name(
+ strings::StrCat("invocation_results[", i, "].end_of_input")));
+ result->notification.Notify();
+ }
+ return Status::OK();
+ }
+
+ private:
+ struct InvocationResult {
+ Notification notification;
+ Status status;
+ std::vector<Tensor> return_values;
+ bool end_of_input;
+ };
+
+ void EnsureRunnerThreadStarted(IteratorContext* ctx)
+ EXCLUSIVE_LOCKS_REQUIRED(mu_) {
+ if (!runner_thread_) {
+ std::shared_ptr<IteratorContext> ctx_copy(new IteratorContext(*ctx));
+ runner_thread_.reset(ctx->env()->StartThread(
+ {}, "runner_thread",
+ std::bind(&ParallelMapIterator::RunnerThread, this, ctx_copy)));
+ }
+ }
+
+ void CallCompleted(const std::shared_ptr<InvocationResult>& result)
+ LOCKS_EXCLUDED(mu_) {
+ {
+ mutex_lock l(mu_);
+ num_calls_--;
+ }
+ result->notification.Notify();
+ cond_var_.notify_all();
+ }
+
+ void CallFunction(const std::shared_ptr<IteratorContext>& ctx,
+ const std::shared_ptr<InvocationResult>& result)
+ LOCKS_EXCLUDED(mu_) {
+ // Get the next input element.
+ std::vector<Tensor> input_element;
+ result->status =
+ input_impl_->GetNext(ctx.get(), &input_element, &result->end_of_input);
+ if (result->end_of_input || !result->status.ok()) {
+ CallCompleted(result);
+ return;
+ }
+
+ // Call `func_(input_element)`, store the result in
+ // `result->return_values`, and notify `result->notification` to unblock
+ // a consumer.
+ auto done = [this, result](Status status) {
+ result->status.Update(status);
+ CallCompleted(result);
+ };
+
+ map_func_(ctx.get(), std::move(input_element), &result->return_values,
+ std::move(done));
+ }
+
+ int64 MaxInvocationResults() { return num_parallel_calls_; }
+
+ Status ProcessResult(const std::shared_ptr<InvocationResult>& result,
+ std::vector<Tensor>* out_tensors,
+ bool* end_of_sequence) {
+ if (!result->end_of_input && result->status.ok()) {
+ *out_tensors = std::move(result->return_values);
+ *end_of_sequence = false;
+ return Status::OK();
+ }
+ if (errors::IsOutOfRange(result->status)) {
+ // `f` may deliberately raise `errors::OutOfRange` to indicate that we
+ // should terminate the iteration early.
+ *end_of_sequence = true;
+ return Status::OK();
+ }
+ *end_of_sequence = result->end_of_input;
+ return result->status;
+ }
+
+ void RunnerThread(const std::shared_ptr<IteratorContext>& ctx) {
+ std::vector<std::shared_ptr<InvocationResult>> new_calls;
+ new_calls.reserve(num_parallel_calls_);
+ while (true) {
+ {
+ mutex_lock l(mu_);
+ while (!cancelled_ &&
+ (num_calls_ >= num_parallel_calls_ ||
+ invocation_results_.size() >= MaxInvocationResults())) {
+ cond_var_.wait(l);
+ }
+ if (cancelled_) {
+ return;
+ }
+ while (num_calls_ < num_parallel_calls_ &&
+ invocation_results_.size() < MaxInvocationResults()) {
+ invocation_results_.emplace_back(new InvocationResult());
+ new_calls.push_back(invocation_results_.back());
+ num_calls_++;
+ }
+ }
+ cond_var_.notify_all();
+ for (const auto& call : new_calls) {
+ CallFunction(ctx, call);
+ }
+ new_calls.clear();
+ }
+ }
+
+ Status WriteStatusLocked(IteratorStateWriter* writer, size_t index,
+ const Status& status) EXCLUSIVE_LOCKS_REQUIRED(mu_) {
+ TF_RETURN_IF_ERROR(
+ writer->WriteScalar(CodeKey(index), static_cast<int64>(status.code())));
+ if (!status.ok()) {
+ TF_RETURN_IF_ERROR(
+ writer->WriteScalar(ErrorMessageKey(index), status.error_message()));
+ }
+ return Status::OK();
+ }
+
+ Status ReadStatusLocked(IteratorStateReader* reader, size_t index,
+ Status* status) EXCLUSIVE_LOCKS_REQUIRED(mu_) {
+ int64 code_int;
+ TF_RETURN_IF_ERROR(reader->ReadScalar(CodeKey(index), &code_int));
+ error::Code code = static_cast<error::Code>(code_int);
+
+ if (code != error::Code::OK) {
+ string error_message;
+ TF_RETURN_IF_ERROR(
+ reader->ReadScalar(ErrorMessageKey(index), &error_message));
+ *status = Status(code, error_message);
+ } else {
+ *status = Status::OK();
+ }
+ return Status::OK();
+ }
+
+ string CodeKey(size_t index) {
+ return full_name(
+ strings::StrCat("invocation_results[", index, "].code"));
+ }
+
+ string ErrorMessageKey(size_t index) {
+ return full_name(
+ strings::StrCat("invocation_results[", index, "].error_message"));
+ }
+
+ const DatasetBase* const input_dataset_; // Not owned.
+ const ParallelMapIteratorFunction map_func_;
+ const int32 num_parallel_calls_;
+ // Used for coordination between the main thread and the runner thread.
+ mutex mu_;
+ // Used for coordination between the main thread and the runner thread. In
+ // particular, the runner thread should only schedule new calls when the
+ // number of in-flight calls is less than the user specified level of
+ // parallelism and there are slots available in the `invocation_results_`
+ // buffer.
+ condition_variable cond_var_;
+ // Counts the number of outstanding calls.
+ int64 num_calls_ GUARDED_BY(mu_) = 0;
+ std::unique_ptr<IteratorBase> input_impl_;
+ // Buffer for storing the invocation results.
+ std::deque<std::shared_ptr<InvocationResult>> invocation_results_
+ GUARDED_BY(mu_);
+ std::unique_ptr<Thread> runner_thread_ GUARDED_BY(mu_);
+ bool cancelled_ GUARDED_BY(mu_) = false;
+};
+
+} // namespace
+
+std::unique_ptr<IteratorBase> NewParallelMapIterator(
+ const DatasetBaseIterator::BaseParams& params,
+ const DatasetBase* input_dataset, ParallelMapIteratorFunction map_func,
+ int32 num_parallel_calls) {
+ return std::unique_ptr<IteratorBase>(new ParallelMapIterator(
+ params, input_dataset, std::move(map_func), num_parallel_calls));
+}
+
+} // namespace tensorflow
diff --git a/tensorflow/core/kernels/data/parallel_map_iterator.h b/tensorflow/core/kernels/data/parallel_map_iterator.h
new file mode 100644
index 0000000000..2ce36c3869
--- /dev/null
+++ b/tensorflow/core/kernels/data/parallel_map_iterator.h
@@ -0,0 +1,44 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+#ifndef TENSORFLOW_CORE_KERNELS_DATA_PARALLEL_MAP_ITERATOR_H_
+#define TENSORFLOW_CORE_KERNELS_DATA_PARALLEL_MAP_ITERATOR_H_
+
+#include <memory>
+
+#include "tensorflow/core/framework/dataset.h"
+
+namespace tensorflow {
+
+// A function that transforms elements of one dataset into another
+// asynchronously. The arguments are:
+// 1. An `IteratorContext*` for the context in which the function should
+// execute.
+// 2. A `std::vector<Tensor>` containing the input element.
+// 3. A `std::vector<Tensor>*` to which the function will write the result.
+// 4. A `StatusCallback` that should be invoked when the function is complete.
+using ParallelMapIteratorFunction =
+ std::function<void(IteratorContext*, std::vector<Tensor>,
+ std::vector<Tensor>*, StatusCallback)>;
+
+// Returns a new iterator that applies `map_func` to the elements of
+// `input_dataset` using the given degree of parallelism.
+std::unique_ptr<IteratorBase> NewParallelMapIterator(
+ const DatasetBaseIterator::BaseParams& params,
+ const DatasetBase* input_dataset, ParallelMapIteratorFunction map_func,
+ int32 num_parallel_calls);
+
+} // namespace tensorflow
+
+#endif // TENSORFLOW_CORE_KERNELS_DATA_PARALLEL_MAP_ITERATOR_H_
diff --git a/tensorflow/core/kernels/fused_batch_norm_op.cc b/tensorflow/core/kernels/fused_batch_norm_op.cc
index f99dd643f7..d89f1592bd 100644
--- a/tensorflow/core/kernels/fused_batch_norm_op.cc
+++ b/tensorflow/core/kernels/fused_batch_norm_op.cc
@@ -45,6 +45,24 @@ struct FusedBatchNorm;
template <typename Device, typename T, typename U>
struct FusedBatchNormGrad;
+template <bool IsSame, typename Y, typename X, typename T>
+struct CastIfNecessary {
+ static inline void process(
+ Y& y, X& x_shifted, const Eigen::DSizes<Eigen::Index, 2>& rest_by_depth,
+ const CPUDevice& d) {
+ y.reshape(rest_by_depth).device(d) = x_shifted.template cast<T>();
+ }
+};
+
+template <typename Y, typename X, typename T>
+struct CastIfNecessary<true, Y, X, T> {
+ static inline void process(
+ Y& y, X& x_shifted, const Eigen::DSizes<Eigen::Index, 2>& rest_by_depth,
+ const CPUDevice& d) {
+ y.reshape(rest_by_depth).device(d) = x_shifted;
+ }
+};
+
template <typename T, typename U>
struct FusedBatchNorm<CPUDevice, T, U> {
void operator()(OpKernelContext* context, const Tensor& x_input,
@@ -125,7 +143,11 @@ struct FusedBatchNorm<CPUDevice, T, U> {
auto x_shifted =
x_scaled + offset.reshape(one_by_depth).broadcast(bcast_spec);
- y.reshape(rest_by_depth).device(d) = x_shifted.template cast<T>();
+ // Explicitly checks the types of T and U and only casts x_shifted when
+ // T != U. (Not doing so caused a 35-50% performance slowdown for
+ // some compiler flags.)
+ CastIfNecessary<std::is_same<T, U>::value, decltype(y), decltype(x_shifted),
+ T>::process(y, x_shifted, rest_by_depth, d);
}
};
diff --git a/tensorflow/core/kernels/mkl_concat_op.cc b/tensorflow/core/kernels/mkl_concat_op.cc
index 6f490cdc23..d8efb1be3e 100644
--- a/tensorflow/core/kernels/mkl_concat_op.cc
+++ b/tensorflow/core/kernels/mkl_concat_op.cc
@@ -308,11 +308,9 @@ class MklConcatOp : public OpKernel {
}
if (invoke_eigen) {
- string msg = std::string("Invoking Eigen version of Concat. Reason:") +
- (!is_concat_dim_channel
- ? std::string("Concat dimension is not channel")
- : std::string("Not all tensors are in Mkl layout"));
- VLOG(1) << "_MklConcatOp: " << msg;
+ VLOG(1) << "_MklConcatOp: Invoking Eigen version of Concat. Reason:"
+ << (!is_concat_dim_channel ? "Concat dimension is not channel"
+ : "Not all tensors are in Mkl layout");
CallEigenVersion(context, input_tensors, input_shapes);
return;
}
diff --git a/tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc b/tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc
index a370037d97..b73a119a88 100644
--- a/tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc
+++ b/tensorflow/core/kernels/mkl_conv_grad_filter_ops.cc
@@ -328,9 +328,8 @@ class MklConv2DBwdFilterPrimitiveFactory : public MklPrimitiveFactory<T> {
return instance_;
}
- static std::string CreateKey(
- const MklConvBwdFilterParams& convBwdFilterDims) {
- std::string prefix = "conv2d_bwd_filter";
+ static string CreateKey(const MklConvBwdFilterParams& convBwdFilterDims) {
+ string prefix = "conv2d_bwd_filter";
FactoryKeyCreator key_creator;
key_creator.AddAsKey(prefix);
key_creator.AddAsKey(convBwdFilterDims.src_dims);
@@ -346,13 +345,13 @@ class MklConv2DBwdFilterPrimitiveFactory : public MklPrimitiveFactory<T> {
MklPrimitive* GetConv2dBwdFilter(
const MklConvBwdFilterParams& convBwdFilterDims) {
- std::string key = CreateKey(convBwdFilterDims);
+ string key = CreateKey(convBwdFilterDims);
return this->GetOp(key);
}
void SetConv2dBwdFilter(
const MklConvBwdFilterParams& convBwdFilterDims, MklPrimitive* op) {
- std::string key = CreateKey(convBwdFilterDims);
+ string key = CreateKey(convBwdFilterDims);
this->SetOp(key, op);
}
};
diff --git a/tensorflow/core/kernels/mkl_conv_grad_input_ops.cc b/tensorflow/core/kernels/mkl_conv_grad_input_ops.cc
index b0f7faaa1a..39498f1a80 100644
--- a/tensorflow/core/kernels/mkl_conv_grad_input_ops.cc
+++ b/tensorflow/core/kernels/mkl_conv_grad_input_ops.cc
@@ -265,9 +265,8 @@ class MklConv2DBwdInputPrimitiveFactory : public MklPrimitiveFactory<T> {
return instance_;
}
- static std::string CreateKey(
- const MklConvBwdInputParams& convBwdInputDims) {
- std::string prefix = "conv2d_bwd_input";
+ static string CreateKey(const MklConvBwdInputParams& convBwdInputDims) {
+ string prefix = "conv2d_bwd_input";
FactoryKeyCreator key_creator;
key_creator.AddAsKey(prefix);
key_creator.AddAsKey(convBwdInputDims.diff_src_dims);
@@ -282,13 +281,13 @@ class MklConv2DBwdInputPrimitiveFactory : public MklPrimitiveFactory<T> {
MklPrimitive* GetConv2dBwdInput(
const MklConvBwdInputParams& convBwdInputDims) {
- std::string key = CreateKey(convBwdInputDims);
+ string key = CreateKey(convBwdInputDims);
return this->GetOp(key);
}
void SetConv2dBwdInput(
const MklConvBwdInputParams& convBwdInputDims, MklPrimitive *op) {
- std::string key = CreateKey(convBwdInputDims);
+ string key = CreateKey(convBwdInputDims);
this->SetOp(key, op);
}
};
diff --git a/tensorflow/core/kernels/mkl_conv_ops.cc b/tensorflow/core/kernels/mkl_conv_ops.cc
index b568973220..62396eeb8b 100644
--- a/tensorflow/core/kernels/mkl_conv_ops.cc
+++ b/tensorflow/core/kernels/mkl_conv_ops.cc
@@ -18,7 +18,6 @@ limitations under the License.
#include <string.h>
#include <map>
-#include <string>
#include <vector>
#include <memory>
@@ -35,6 +34,7 @@ limitations under the License.
#include "tensorflow/core/lib/gtl/array_slice.h"
#include "tensorflow/core/lib/strings/numbers.h"
#include "tensorflow/core/lib/strings/str_util.h"
+#include "tensorflow/core/lib/strings/strcat.h"
#include "tensorflow/core/platform/logging.h"
#include "tensorflow/core/platform/macros.h"
#include "tensorflow/core/util/padding.h"
@@ -298,8 +298,8 @@ class MklConv2DFwdPrimitiveFactory : public MklPrimitiveFactory<T> {
return instance_;
}
- static std::string CreateKey(const MklConvFwdParams& convFwdDims) {
- std::string prefix = "conv2d_fwd_";
+ static string CreateKey(const MklConvFwdParams& convFwdDims) {
+ string prefix = "conv2d_fwd_";
FactoryKeyCreator key_creator;
key_creator.AddAsKey(prefix);
key_creator.AddAsKey(convFwdDims.src_dims);
@@ -314,12 +314,12 @@ class MklConv2DFwdPrimitiveFactory : public MklPrimitiveFactory<T> {
}
MklPrimitive* GetConv2DFwd(const MklConvFwdParams& convFwdDims) {
- std::string key = CreateKey(convFwdDims);
+ string key = CreateKey(convFwdDims);
return this->GetOp(key);
}
void SetConv2DFwd(const MklConvFwdParams& convFwdDims, MklPrimitive* op) {
- std::string key = CreateKey(convFwdDims);
+ string key = CreateKey(convFwdDims);
this->SetOp(key, op);
}
};
@@ -930,10 +930,9 @@ class MklConv2DOp : public OpKernel {
conv2d_fwd->Execute(src_data, filter_data, dst_data);
}
} catch (mkldnn::error &e) {
- string error_msg = "Status: " + std::to_string(e.status) +
- ", message: " + std::string(e.message) +
- ", in file " + std::string(__FILE__) + ":" +
- std::to_string(__LINE__);
+ string error_msg = tensorflow::strings::StrCat(
+ "Status: ", e.status, ", message: ", string(e.message), ", in file ",
+ __FILE__, ":", __LINE__);
OP_REQUIRES_OK(context,
errors::Aborted("Operation received an exception:", error_msg));
}
diff --git a/tensorflow/core/kernels/mkl_conv_ops.h b/tensorflow/core/kernels/mkl_conv_ops.h
index 5e1a5001dc..3f154ff33b 100644
--- a/tensorflow/core/kernels/mkl_conv_ops.h
+++ b/tensorflow/core/kernels/mkl_conv_ops.h
@@ -17,7 +17,6 @@ limitations under the License.
#define TENSORFLOW_CORE_KERNELS_MKL_CONV_OPS_H_
#include <limits>
-#include <string>
#include <vector>
#include <memory>
diff --git a/tensorflow/core/kernels/mkl_pooling_ops_common.h b/tensorflow/core/kernels/mkl_pooling_ops_common.h
index c0dfed7d7d..cb1eecb36a 100644
--- a/tensorflow/core/kernels/mkl_pooling_ops_common.h
+++ b/tensorflow/core/kernels/mkl_pooling_ops_common.h
@@ -17,7 +17,6 @@ limitations under the License.
#define TENSORFLOW_CORE_KERNELS_MKL_POOLING_OPS_COMMON_H_
#ifdef INTEL_MKL
-#include <string>
#include <vector>
#include "tensorflow/core/util/mkl_util.h"
#include "tensorflow/core/util/padding.h"
diff --git a/tensorflow/core/kernels/mkl_reshape_op.cc b/tensorflow/core/kernels/mkl_reshape_op.cc
index 02ea9fc068..9c536df215 100644
--- a/tensorflow/core/kernels/mkl_reshape_op.cc
+++ b/tensorflow/core/kernels/mkl_reshape_op.cc
@@ -152,8 +152,12 @@ class MklReshapeOp : public OpKernel {
// If Tensorflow's data format and the underlying format maintained by
// MKLDNN are equivalent (both are NHWC or both are NCHW), then we can
// safely return true.
+ // @todo: Future do not force skip reorder for all blocked format. Use
+ // blocking_desc_is_equal() for checking all the stride arrays in
+ // mkl-dnn/blob/master/src/common/type_helpers.hpp
auto input_mkl_md = mkl_shape_input.GetMklLayout();
- if (mkl_shape_input.GetTfDataFormat() == input_mkl_md.data.format) {
+ if (mkl_shape_input.GetTfDataFormat() == input_mkl_md.data.format &&
+ mkl_shape_input.GetTfDataFormat() != memory::format::blocked) {
ret = true;
}
diff --git a/tensorflow/core/kernels/non_max_suppression_op.cc b/tensorflow/core/kernels/non_max_suppression_op.cc
index f59843a07a..c7d0d4de0d 100644
--- a/tensorflow/core/kernels/non_max_suppression_op.cc
+++ b/tensorflow/core/kernels/non_max_suppression_op.cc
@@ -121,10 +121,11 @@ static inline std::function<bool(int, int)> CreateOverlapsSuppressCheckFn(
std::placeholders::_1, std::placeholders::_2, threshold);
}
-void DoNonMaxSuppressionOp(OpKernelContext* context, const Tensor& scores,
- int num_boxes, const Tensor& max_output_size,
- const float score_threshold,
- std::function<bool(int, int)> suppress_check_fn) {
+void DoNonMaxSuppressionOp(
+ OpKernelContext* context, const Tensor& scores, int num_boxes,
+ const Tensor& max_output_size, const float score_threshold,
+ const std::function<bool(int, int)>& suppress_check_fn,
+ bool pad_to_max_output_size = false, int* ptr_num_valid_outputs = nullptr) {
const int output_size = std::min(max_output_size.scalar<int>()(), num_boxes);
std::vector<float> scores_data(num_boxes);
@@ -172,6 +173,15 @@ void DoNonMaxSuppressionOp(OpKernelContext* context, const Tensor& scores,
}
}
+ int num_valid_outputs = selected.size();
+ if (pad_to_max_output_size) {
+ selected.resize(output_size, 0);
+ selected_scores.resize(output_size, 0);
+ }
+ if (ptr_num_valid_outputs) {
+ *ptr_num_valid_outputs = num_valid_outputs;
+ }
+
// Allocate output tensors
Tensor* output_indices = nullptr;
TensorShape output_shape({static_cast<int>(selected.size())});
@@ -262,54 +272,106 @@ class NonMaxSuppressionV2Op : public OpKernel {
}
};
-template <typename Device>
-class NonMaxSuppressionV3Op : public OpKernel {
+class NonMaxSuppressionV3V4Base : public OpKernel {
public:
- explicit NonMaxSuppressionV3Op(OpKernelConstruction* context)
+ explicit NonMaxSuppressionV3V4Base(OpKernelConstruction* context)
: OpKernel(context) {}
void Compute(OpKernelContext* context) override {
// boxes: [num_boxes, 4]
- const Tensor& boxes = context->input(0);
+ boxes_ = context->input(0);
// scores: [num_boxes]
- const Tensor& scores = context->input(1);
+ scores_ = context->input(1);
// max_output_size: scalar
- const Tensor& max_output_size = context->input(2);
+ max_output_size_ = context->input(2);
OP_REQUIRES(
- context, TensorShapeUtils::IsScalar(max_output_size.shape()),
+ context, TensorShapeUtils::IsScalar(max_output_size_.shape()),
errors::InvalidArgument("max_output_size must be 0-D, got shape ",
- max_output_size.shape().DebugString()));
+ max_output_size_.shape().DebugString()));
// iou_threshold: scalar
const Tensor& iou_threshold = context->input(3);
OP_REQUIRES(context, TensorShapeUtils::IsScalar(iou_threshold.shape()),
errors::InvalidArgument("iou_threshold must be 0-D, got shape ",
iou_threshold.shape().DebugString()));
- const float iou_threshold_val = iou_threshold.scalar<float>()();
-
+ iou_threshold_val_ = iou_threshold.scalar<float>()();
+ OP_REQUIRES(context, iou_threshold_val_ >= 0 && iou_threshold_val_ <= 1,
+ errors::InvalidArgument("iou_threshold must be in [0, 1]"));
// score_threshold: scalar
const Tensor& score_threshold = context->input(4);
OP_REQUIRES(
context, TensorShapeUtils::IsScalar(score_threshold.shape()),
errors::InvalidArgument("score_threshold must be 0-D, got shape ",
score_threshold.shape().DebugString()));
- const float score_threshold_val = score_threshold.scalar<float>()();
+ score_threshold_val_ = score_threshold.scalar<float>()();
- OP_REQUIRES(context, iou_threshold_val >= 0 && iou_threshold_val <= 1,
- errors::InvalidArgument("iou_threshold must be in [0, 1]"));
- int num_boxes = 0;
- ParseAndCheckBoxSizes(context, boxes, &num_boxes);
- CheckScoreSizes(context, num_boxes, scores);
+ num_boxes_ = 0;
+ ParseAndCheckBoxSizes(context, boxes_, &num_boxes_);
+ CheckScoreSizes(context, num_boxes_, scores_);
if (!context->status().ok()) {
return;
}
- auto suppress_check_fn = CreateIOUSuppressCheckFn(boxes, iou_threshold_val);
- DoNonMaxSuppressionOp(context, scores, num_boxes, max_output_size,
- score_threshold_val, suppress_check_fn);
+ DoComputeAndPostProcess(context);
+ }
+
+ protected:
+ virtual void DoComputeAndPostProcess(OpKernelContext* context) = 0;
+
+ Tensor boxes_;
+ Tensor scores_;
+ Tensor max_output_size_;
+ int num_boxes_;
+ float iou_threshold_val_;
+ float score_threshold_val_;
+};
+
+template <typename Device>
+class NonMaxSuppressionV3Op : public NonMaxSuppressionV3V4Base {
+ public:
+ explicit NonMaxSuppressionV3Op(OpKernelConstruction* context)
+ : NonMaxSuppressionV3V4Base(context) {}
+
+ protected:
+ void DoComputeAndPostProcess(OpKernelContext* context) override {
+ auto suppress_check_fn =
+ CreateIOUSuppressCheckFn(boxes_, iou_threshold_val_);
+
+ DoNonMaxSuppressionOp(context, scores_, num_boxes_, max_output_size_,
+ score_threshold_val_, suppress_check_fn);
}
};
template <typename Device>
+class NonMaxSuppressionV4Op : public NonMaxSuppressionV3V4Base {
+ public:
+ explicit NonMaxSuppressionV4Op(OpKernelConstruction* context)
+ : NonMaxSuppressionV3V4Base(context) {
+ OP_REQUIRES_OK(context, context->GetAttr("pad_to_max_output_size",
+ &pad_to_max_output_size_));
+ }
+
+ protected:
+ void DoComputeAndPostProcess(OpKernelContext* context) override {
+ auto suppress_check_fn =
+ CreateIOUSuppressCheckFn(boxes_, iou_threshold_val_);
+ int num_valid_outputs;
+
+ DoNonMaxSuppressionOp(context, scores_, num_boxes_, max_output_size_,
+ score_threshold_val_, suppress_check_fn,
+ pad_to_max_output_size_, &num_valid_outputs);
+
+ // Allocate scalar output tensor for number of indices computed.
+ Tensor* num_outputs_t = nullptr;
+ OP_REQUIRES_OK(context, context->allocate_output(
+ 1, tensorflow::TensorShape{}, &num_outputs_t));
+ num_outputs_t->scalar<int32>().setConstant(num_valid_outputs);
+ }
+
+ private:
+ bool pad_to_max_output_size_;
+};
+
+template <typename Device>
class NonMaxSuppressionWithOverlapsOp : public OpKernel {
public:
explicit NonMaxSuppressionWithOverlapsOp(OpKernelConstruction* context)
@@ -365,6 +427,9 @@ REGISTER_KERNEL_BUILDER(Name("NonMaxSuppressionV2").Device(DEVICE_CPU),
REGISTER_KERNEL_BUILDER(Name("NonMaxSuppressionV3").Device(DEVICE_CPU),
NonMaxSuppressionV3Op<CPUDevice>);
+REGISTER_KERNEL_BUILDER(Name("NonMaxSuppressionV4").Device(DEVICE_CPU),
+ NonMaxSuppressionV4Op<CPUDevice>);
+
REGISTER_KERNEL_BUILDER(
Name("NonMaxSuppressionWithOverlaps").Device(DEVICE_CPU),
NonMaxSuppressionWithOverlapsOp<CPUDevice>);
diff --git a/tensorflow/core/kernels/non_max_suppression_op_test.cc b/tensorflow/core/kernels/non_max_suppression_op_test.cc
index 055161a35f..c321849f40 100644
--- a/tensorflow/core/kernels/non_max_suppression_op_test.cc
+++ b/tensorflow/core/kernels/non_max_suppression_op_test.cc
@@ -570,6 +570,61 @@ TEST_F(NonMaxSuppressionV3OpTest, TestEmptyInput) {
}
//
+// NonMaxSuppressionV4Op Tests
+//
+
+class NonMaxSuppressionV4OpTest : public OpsTestBase {
+ protected:
+ void MakeOp() {
+ TF_EXPECT_OK(NodeDefBuilder("non_max_suppression_op", "NonMaxSuppressionV4")
+ .Input(FakeInput(DT_FLOAT))
+ .Input(FakeInput(DT_FLOAT))
+ .Input(FakeInput(DT_INT32))
+ .Input(FakeInput(DT_FLOAT))
+ .Input(FakeInput(DT_FLOAT))
+ .Attr("pad_to_max_output_size", true)
+ .Finalize(node_def()));
+ TF_EXPECT_OK(InitOp());
+ }
+};
+
+TEST_F(NonMaxSuppressionV4OpTest, TestSelectFromThreeClustersPadFive) {
+ MakeOp();
+ AddInputFromArray<float>(
+ TensorShape({6, 4}),
+ {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f,
+ 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101});
+ AddInputFromArray<float>(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f});
+ AddInputFromArray<int>(TensorShape({}), {5});
+ AddInputFromArray<float>(TensorShape({}), {.5f});
+ AddInputFromArray<float>(TensorShape({}), {0.0f});
+ TF_ASSERT_OK(RunOpKernel());
+
+ const auto expected_indices = test::AsTensor<int>({3, 0, 5, 0, 0});
+ test::ExpectTensorEqual<int>(expected_indices, *GetOutput(0));
+ Tensor expected_num_valid = test::AsScalar<int>(3);
+ test::ExpectTensorEqual<int>(expected_num_valid, *GetOutput(1));
+}
+
+TEST_F(NonMaxSuppressionV4OpTest, TestSelectFromThreeClustersPadFiveScoreThr) {
+ MakeOp();
+ AddInputFromArray<float>(
+ TensorShape({6, 4}),
+ {0, 0, 1, 1, 0, 0.1f, 1, 1.1f, 0, -0.1f, 1, 0.9f,
+ 0, 10, 1, 11, 0, 10.1f, 1, 11.1f, 0, 100, 1, 101});
+ AddInputFromArray<float>(TensorShape({6}), {.9f, .75f, .6f, .95f, .5f, .3f});
+ AddInputFromArray<int>(TensorShape({}), {6});
+ AddInputFromArray<float>(TensorShape({}), {.5f});
+ AddInputFromArray<float>(TensorShape({}), {0.4f});
+ TF_ASSERT_OK(RunOpKernel());
+
+ const auto expected_indices = test::AsTensor<int>({3, 0, 0, 0, 0, 0});
+ test::ExpectTensorEqual<int>(expected_indices, *GetOutput(0));
+ Tensor expected_num_valid = test::AsScalar<int>(2);
+ test::ExpectTensorEqual<int>(expected_num_valid, *GetOutput(1));
+}
+
+//
// NonMaxSuppressionWithOverlapsOp Tests
//
diff --git a/tensorflow/core/kernels/resource_variable_ops.cc b/tensorflow/core/kernels/resource_variable_ops.cc
index c5292e1ae1..cab9eb729d 100644
--- a/tensorflow/core/kernels/resource_variable_ops.cc
+++ b/tensorflow/core/kernels/resource_variable_ops.cc
@@ -213,64 +213,32 @@ class AssignVariableOp : public OpKernel {
"Variable and value dtypes don't match; respectively, ",
dtype_, " and ", context->input(1).dtype()));
Var* variable = nullptr;
- OP_REQUIRES_OK(
- context,
- LookupOrCreateResource<Var>(
- context, HandleFromInput(context, 0), &variable,
- [this, context](Var** ptr) {
- *ptr = new Var(dtype_);
- PersistentTensor unused;
- Tensor* tmp;
- AllocatorAttributes attr;
- if (!relax_constraints_) {
- attr.set_gpu_compatible(true);
- attr.set_nic_compatible(true);
- }
- TF_RETURN_IF_ERROR(context->allocate_persistent(
- dtype_, context->input(1).shape(), &unused, &tmp, attr));
- *(*ptr)->tensor() = *tmp;
- return Status::OK();
- }));
+ const Tensor& value = context->input(1);
+ // Note: every resource-variable-manipulating op assumes copy-on-write
+ // semantics, and creates a copy of the variable's Tensor if its refcount is
+ // bigger than 1 when we try to modify it. This means we never need to copy
+ // the original tensor for AssignVariableOp; even if there are other live
+ // users of it we know none can modify it so this is always safe (even in
+ // esoteric cases where the same tensor is used to initialize multiple
+ // variables or the tensor is a constant this is safe, as future writes will
+ // trigger copies).
+ OP_REQUIRES_OK(context, LookupOrCreateResource<Var>(
+ context, HandleFromInput(context, 0), &variable,
+ [this, &value](Var** ptr) {
+ *ptr = new Var(dtype_);
+ *(*ptr)->tensor() = value;
+ (*ptr)->is_initialized = true;
+ return Status::OK();
+ }));
core::ScopedUnref s(variable);
-
OP_REQUIRES(context, variable->tensor()->dtype() == dtype_,
errors::InvalidArgument(
"Trying to assign variable with wrong dtype. Expected ",
DataTypeString(variable->tensor()->dtype()), " got ",
DataTypeString(dtype_)));
-
- const Tensor& value = context->input(1);
- AllocatorAttributes attr;
- if (!relax_constraints_) {
- attr.set_gpu_compatible(true);
- attr.set_nic_compatible(true);
- }
-
- // Copying is unnecessary if we are the last user of the value
- // tensor, we can just adopt the input tensor's buffer instead.
- std::unique_ptr<Tensor> input_alias = context->forward_input(
- 1, OpKernelContext::Params::kNoReservation /*output_index*/, dtype_,
- value.shape(), DEVICE_MEMORY, attr);
mutex_lock ml(*variable->mu());
variable->is_initialized = true;
- if (input_alias) {
- *variable->tensor() = *input_alias;
- return;
- }
-
- // Need to copy, but maybe we can re-use variable's buffer?
- if (!variable->tensor()->RefCountIsOne() ||
- !variable->tensor()->shape().IsSameSize(value.shape())) {
- // Copy to new buffer
- PersistentTensor unused;
- Tensor* tmp;
- OP_REQUIRES_OK(context, context->allocate_persistent(
- dtype_, value.shape(), &unused, &tmp, attr));
- *variable->tensor() = *tmp;
- }
- functor::DenseUpdate<Device, T, ASSIGN> copy_functor;
- copy_functor(context->eigen_device<Device>(), variable->tensor()->flat<T>(),
- value.flat<T>());
+ *variable->tensor() = value;
}
private:
diff --git a/tensorflow/core/kernels/save_restore_tensor.cc b/tensorflow/core/kernels/save_restore_tensor.cc
index 990bd2bff9..7930ce4615 100644
--- a/tensorflow/core/kernels/save_restore_tensor.cc
+++ b/tensorflow/core/kernels/save_restore_tensor.cc
@@ -23,6 +23,7 @@ limitations under the License.
#include "tensorflow/core/framework/register_types.h"
#include "tensorflow/core/framework/types.h"
#include "tensorflow/core/kernels/bounds_check.h"
+#include "tensorflow/core/lib/core/threadpool.h"
#include "tensorflow/core/lib/gtl/array_slice.h"
#include "tensorflow/core/lib/strings/strcat.h"
#include "tensorflow/core/lib/strings/stringprintf.h"
@@ -226,43 +227,53 @@ void RestoreTensor(OpKernelContext* context,
#undef READER_COPY
}
-Status RestoreTensorsV2(OpKernelContext* context, const Tensor& prefix,
- const Tensor& tensor_names,
- const Tensor& shape_and_slices,
- gtl::ArraySlice<DataType> dtypes) {
- const string& prefix_string = prefix.scalar<string>()();
+namespace {
- const auto& tensor_names_flat = tensor_names.flat<string>();
- const auto& shape_and_slices_flat = shape_and_slices.flat<string>();
+// Tensors larger than this threshold will be restored from a thread-pool.
+const int64 kLargeShapeThreshold = 16 << 20; // 16M
- // Sort lookup keys to improve locality when reading multiple tensors.
- std::vector<size_t> sorted_name_idx(tensor_names_flat.size());
- std::iota(sorted_name_idx.begin(), sorted_name_idx.end(), 0);
- std::sort(sorted_name_idx.begin(), sorted_name_idx.end(),
- [&tensor_names_flat](size_t a, size_t b) {
- return tensor_names_flat(a) < tensor_names_flat(b);
- });
+// A restore operation for a single tensor. Small tensors may be restored
+// directly from the op thread to improve read locality. Large tensors can be
+// restored from a thread pool: this requires creating a separate BundleReader
+// for each restore.
+struct RestoreOp {
+ RestoreOp& operator=(const RestoreOp&) = delete;
- BundleReader reader(Env::Default(), prefix_string);
- TF_RETURN_IF_ERROR(reader.status());
+ bool should_run_in_pool(BundleReader* reader) const {
+ TensorShape restored_full_shape;
- // TODO(zongheng): potential optimization: one Seek() in first lookup.
- // TODO(zongheng): consider measuring speed and issuing concurrent lookups
- // within a fixed memory budget.
- TensorShape restored_full_shape;
- Tensor* restored_tensor = nullptr;
- for (auto i : sorted_name_idx) {
- const string& tensor_name = tensor_names_flat(i);
- const string& shape_and_slice = shape_and_slices_flat(i);
+ // Ignore status here; we'll catch the error later.
+ if (!reader->LookupTensorShape(tensor_name, &restored_full_shape).ok()) {
+ return false;
+ }
+ return restored_full_shape.num_elements() > kLargeShapeThreshold;
+ }
+
+ // Run this restore operation using a new BundleReader.
+ void run_with_new_reader() {
+ BundleReader reader(Env::Default(), reader_prefix);
+ if (!reader.status().ok()) {
+ status = reader.status();
+ return;
+ }
+
+ status = run(&reader);
+ }
+
+ Status run(BundleReader* reader) {
+ TensorShape restored_full_shape;
TF_RETURN_IF_ERROR(
- reader.LookupTensorShape(tensor_name, &restored_full_shape));
+ reader->LookupTensorShape(tensor_name, &restored_full_shape));
+ VLOG(1) << "Restoring tensor " << idx << " : " << tensor_name << " : "
+ << restored_full_shape.num_elements();
+ Tensor* restored_tensor;
if (shape_and_slice.empty()) {
// Lookup the full tensor.
TF_RETURN_IF_ERROR(
- context->allocate_output(i, restored_full_shape, &restored_tensor));
- TF_RETURN_IF_ERROR(reader.Lookup(tensor_name, restored_tensor));
+ context->allocate_output(idx, restored_full_shape, &restored_tensor));
+ TF_RETURN_IF_ERROR(reader->Lookup(tensor_name, restored_tensor));
} else {
// Lookup the slice.
TensorShape parsed_full_shape;
@@ -272,6 +283,7 @@ Status RestoreTensorsV2(OpKernelContext* context, const Tensor& prefix,
TF_RETURN_IF_ERROR(
checkpoint::ParseShapeAndSlice(shape_and_slice, &parsed_full_shape,
&parsed_slice, &parsed_slice_shape));
+
if (!restored_full_shape.IsSameSize(parsed_full_shape)) {
return errors::InvalidArgument(
"tensor_name = ", tensor_name, "; shape in shape_and_slice spec ",
@@ -279,19 +291,93 @@ Status RestoreTensorsV2(OpKernelContext* context, const Tensor& prefix,
" does not match the shape stored in checkpoint: ",
restored_full_shape.DebugString());
}
-
TF_RETURN_IF_ERROR(
- context->allocate_output(i, parsed_slice_shape, &restored_tensor));
+ context->allocate_output(idx, parsed_slice_shape, &restored_tensor));
TF_RETURN_IF_ERROR(
- reader.LookupSlice(tensor_name, parsed_slice, restored_tensor));
+ reader->LookupSlice(tensor_name, parsed_slice, restored_tensor));
+ }
+ return Status::OK();
+ }
+
+ OpKernelContext* context;
+ size_t idx;
+ string tensor_name;
+ string shape_and_slice;
+ string reader_prefix;
+
+ ::tensorflow::Status status;
+};
+
+} // namespace
+
+Status RestoreTensorsV2(OpKernelContext* context, const Tensor& prefix,
+ const Tensor& tensor_names,
+ const Tensor& shape_and_slices,
+ gtl::ArraySlice<DataType> dtypes) {
+ const string& prefix_string = prefix.scalar<string>()();
+
+ const auto& tensor_names_flat = tensor_names.flat<string>();
+ const auto& shape_and_slices_flat = shape_and_slices.flat<string>();
+
+ // Sort lookup keys to improve locality when reading multiple tensors.
+ std::vector<size_t> sorted_name_idx(tensor_names_flat.size());
+ std::iota(sorted_name_idx.begin(), sorted_name_idx.end(), 0);
+ std::sort(sorted_name_idx.begin(), sorted_name_idx.end(),
+ [&tensor_names_flat](size_t a, size_t b) {
+ return tensor_names_flat(a) < tensor_names_flat(b);
+ });
+
+ std::vector<std::unique_ptr<RestoreOp> > pool_restore_ops;
+ std::vector<std::unique_ptr<RestoreOp> > direct_restore_ops;
+
+ BundleReader default_reader(Env::Default(), prefix_string);
+ TF_RETURN_IF_ERROR(default_reader.status());
+
+ for (auto i : sorted_name_idx) {
+ const string& tensor_name = tensor_names_flat(i);
+ const string& shape_and_slice = shape_and_slices_flat(i);
+ auto op =
+ new RestoreOp{context, i, tensor_name, shape_and_slice, prefix_string};
+ if (op->should_run_in_pool(&default_reader)) {
+ pool_restore_ops.emplace_back(op);
+ } else {
+ direct_restore_ops.emplace_back(op);
}
- if (dtypes[i] != restored_tensor->dtype()) {
+ }
+
+ {
+ // Schedule any threaded operations first, skipping thread pool creation if
+ // we don't have any expensive operations.
+ std::unique_ptr<thread::ThreadPool> reader_pool;
+ if (!pool_restore_ops.empty()) {
+ reader_pool.reset(
+ new thread::ThreadPool(Env::Default(), "restore_tensors", 8));
+ for (auto& op : pool_restore_ops) {
+ reader_pool->Schedule([&op]() { op->run_with_new_reader(); });
+ }
+ }
+
+ // Read small tensors from the op thread
+ for (auto& op : direct_restore_ops) {
+ TF_RETURN_IF_ERROR(op->run(&default_reader));
+ }
+ }
+
+ // Check status of pool ops; this must come after the pool shuts down.
+ for (auto& op : pool_restore_ops) {
+ TF_RETURN_IF_ERROR(op->status);
+ }
+
+ for (auto i : sorted_name_idx) {
+ const string& tensor_name = tensor_names_flat(i);
+ if (dtypes[i] != context->mutable_output(i)->dtype()) {
return errors::InvalidArgument(
"tensor_name = ", tensor_name, "; expected dtype ",
DataTypeString(dtypes[i]), " does not equal restored dtype ",
- DataTypeString(restored_tensor->dtype()));
+ DataTypeString(context->mutable_output(i)->dtype()));
}
}
+
return Status::OK();
}
diff --git a/tensorflow/core/kernels/strided_slice_op.cc b/tensorflow/core/kernels/strided_slice_op.cc
index 1e3e92a68a..59fdc2262a 100644
--- a/tensorflow/core/kernels/strided_slice_op.cc
+++ b/tensorflow/core/kernels/strided_slice_op.cc
@@ -32,6 +32,7 @@ limitations under the License.
#include "tensorflow/core/framework/tensor.h"
#include "tensorflow/core/kernels/bounds_check.h"
#include "tensorflow/core/kernels/ops_util.h"
+#include "tensorflow/core/kernels/training_op_helpers.h"
#include "tensorflow/core/kernels/variable_ops.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/lib/gtl/array_slice.h"
@@ -304,6 +305,9 @@ class StridedSliceAssignOp : public OpKernel {
Var* v;
OP_REQUIRES_OK(context,
LookupResource(context, HandleFromInput(context, 0), &v));
+ mutex_lock ml(*v->mu());
+ OP_REQUIRES_OK(context,
+ PrepareToUpdateVariable<Device, T>(context, v->tensor()));
old_lhs = *v->tensor();
OP_REQUIRES(context, old_lhs.dtype() == DataTypeToEnum<T>::value,
errors::InvalidArgument(
diff --git a/tensorflow/core/kernels/training_op_helpers.cc b/tensorflow/core/kernels/training_op_helpers.cc
index f288e124ee..d3c4f62071 100644
--- a/tensorflow/core/kernels/training_op_helpers.cc
+++ b/tensorflow/core/kernels/training_op_helpers.cc
@@ -39,8 +39,15 @@ mutex* GetTrainingVariableMutex(OpKernelContext* ctx, int input) {
// GetInputTensor which will signal a failure.
std::vector<mutex_lock> MaybeLockVariableInputMutexesInOrder(
OpKernelContext* ctx, bool do_lock, const std::vector<int>& input_ids) {
+ bool any_resource = false;
+ for (auto i : input_ids) {
+ if (ctx->input_dtype(i) == DT_RESOURCE) {
+ any_resource = true;
+ break;
+ }
+ }
std::vector<mutex_lock> locks;
- if (!do_lock) {
+ if (!do_lock && !any_resource) {
return locks;
}
std::vector<mutex*> mutexes;
diff --git a/tensorflow/core/kernels/training_op_helpers.h b/tensorflow/core/kernels/training_op_helpers.h
index 7e56e15450..765335d3a0 100644
--- a/tensorflow/core/kernels/training_op_helpers.h
+++ b/tensorflow/core/kernels/training_op_helpers.h
@@ -80,18 +80,8 @@ Status GetInputTensorFromVariable(OpKernelContext* ctx, int input,
Var* var;
TF_RETURN_IF_ERROR(LookupResource(ctx, HandleFromInput(ctx, input), &var));
core::ScopedUnref unref_var(var);
- if (lock_held) {
- TF_RETURN_IF_ERROR(
- PrepareToUpdateVariable<Device, T>(ctx, var->tensor()));
- *out = *var->tensor();
- } else {
- mutex_lock ml(*var->mu());
- if (!sparse) {
- TF_RETURN_IF_ERROR(
- PrepareToUpdateVariable<Device, T>(ctx, var->tensor()));
- }
- *out = *var->tensor();
- }
+ TF_RETURN_IF_ERROR(PrepareToUpdateVariable<Device, T>(ctx, var->tensor()));
+ *out = *var->tensor();
return Status::OK();
}
*out = ctx->mutable_input(input, lock_held);
diff --git a/tensorflow/core/lib/bfloat16/bfloat16.h b/tensorflow/core/lib/bfloat16/bfloat16.h
index 1c130ba300..d6f3f26cd5 100644
--- a/tensorflow/core/lib/bfloat16/bfloat16.h
+++ b/tensorflow/core/lib/bfloat16/bfloat16.h
@@ -45,17 +45,25 @@ typedef std::complex<double> complex128;
struct bfloat16 {
B16_DEVICE_FUNC bfloat16() {}
- B16_DEVICE_FUNC explicit bfloat16(const float v) {
+ B16_DEVICE_FUNC static bfloat16 truncate_to_bfloat16(const float v) {
+ bfloat16 output;
if (float_isnan(v)) {
- value = NAN_VALUE;
- return;
+ output.value = NAN_VALUE;
+ return output;
}
const uint16_t* p = reinterpret_cast<const uint16_t*>(&v);
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
- value = p[0];
+ output.value = p[0];
#else
- value = p[1];
+ output.value = p[1];
#endif
+ return output;
+ }
+
+ B16_DEVICE_FUNC explicit bfloat16(const float v) {
+ // TODO(asabne) : change the below line to
+ // value = round_to_bfloat16(v).value;
+ value = truncate_to_bfloat16(v).value;
}
B16_DEVICE_FUNC explicit bfloat16(const double val)
@@ -169,8 +177,6 @@ struct bfloat16 {
// Converts a float point to bfloat16, with round-nearest-to-even as rounding
// method.
- // TODO(b/69266521): Add a truncate_to_bfloat16 function and make this
- // function as default behavior.
// TODO: There is a slightly faster implementation (8% faster on CPU)
// than this (documented in cl/175987786), that is exponentially harder to
// understand and document. Switch to the faster version when converting to
diff --git a/tensorflow/core/lib/core/errors.h b/tensorflow/core/lib/core/errors.h
index 51c09032df..a631d9815a 100644
--- a/tensorflow/core/lib/core/errors.h
+++ b/tensorflow/core/lib/core/errors.h
@@ -19,6 +19,7 @@ limitations under the License.
#include <sstream>
#include "tensorflow/core/lib/core/status.h"
+#include "tensorflow/core/lib/strings/str_util.h"
#include "tensorflow/core/lib/strings/strcat.h"
#include "tensorflow/core/platform/logging.h"
#include "tensorflow/core/platform/macros.h"
@@ -118,6 +119,25 @@ DECLARE_ERROR(Unauthenticated, UNAUTHENTICATED)
#undef DECLARE_ERROR
+// Produces a formatted string pattern from the name which can uniquely identify
+// this node upstream to produce an informative error message. The pattern
+// followed is: {{node <name>}}
+// Note: The pattern below determines the regex _NODEDEF_NAME_RE in the file
+// tensorflow/python/client/session.py
+// LINT.IfChange
+inline string FormatNodeNameForError(const string& name) {
+ return strings::StrCat("{{node ", name, "}}");
+}
+// LINT.ThenChange(//tensorflow/python/client/session.py)
+template <typename T>
+string FormatNodeNamesForError(const T& names) {
+ ::tensorflow::str_util::Formatter<string> f(
+ [](string* output, const string& s) {
+ ::tensorflow::strings::StrAppend(output, FormatNodeNameForError(s));
+ });
+ return ::tensorflow::str_util::Join(names, ", ", f);
+}
+
// The CanonicalCode() for non-errors.
using ::tensorflow::error::OK;
diff --git a/tensorflow/core/ops/compat/ops_history.v1.pbtxt b/tensorflow/core/ops/compat/ops_history.v1.pbtxt
index 69351cd392..267af8b976 100644
--- a/tensorflow/core/ops/compat/ops_history.v1.pbtxt
+++ b/tensorflow/core/ops/compat/ops_history.v1.pbtxt
@@ -12074,6 +12074,32 @@ op {
}
}
op {
+ name: "Cast"
+ input_arg {
+ name: "x"
+ type_attr: "SrcT"
+ }
+ output_arg {
+ name: "y"
+ type_attr: "DstT"
+ }
+ attr {
+ name: "SrcT"
+ type: "type"
+ }
+ attr {
+ name: "DstT"
+ type: "type"
+ }
+ attr {
+ name: "Truncate"
+ type: "bool"
+ default_value {
+ b: false
+ }
+ }
+}
+op {
name: "Ceil"
input_arg {
name: "x"
@@ -35444,6 +35470,44 @@ op {
}
}
op {
+ name: "NonMaxSuppressionV4"
+ input_arg {
+ name: "boxes"
+ type: DT_FLOAT
+ }
+ input_arg {
+ name: "scores"
+ type: DT_FLOAT
+ }
+ input_arg {
+ name: "max_output_size"
+ type: DT_INT32
+ }
+ input_arg {
+ name: "iou_threshold"
+ type: DT_FLOAT
+ }
+ input_arg {
+ name: "score_threshold"
+ type: DT_FLOAT
+ }
+ output_arg {
+ name: "selected_indices"
+ type: DT_INT32
+ }
+ output_arg {
+ name: "valid_outputs"
+ type: DT_INT32
+ }
+ attr {
+ name: "pad_to_max_output_size"
+ type: "bool"
+ default_value {
+ b: false
+ }
+ }
+}
+op {
name: "NonMaxSuppressionWithOverlaps"
input_arg {
name: "overlaps"
diff --git a/tensorflow/core/ops/image_ops.cc b/tensorflow/core/ops/image_ops.cc
index 50ced1ff73..31267f72b8 100644
--- a/tensorflow/core/ops/image_ops.cc
+++ b/tensorflow/core/ops/image_ops.cc
@@ -442,8 +442,9 @@ REGISTER_OP("DrawBoundingBoxes")
if (c->ValueKnown(c->Dim(images, 3))) {
int64 depth = c->Value(c->Dim(images, 3));
if (!(depth == 1 || depth == 3 || depth == 4)) {
- return errors::InvalidArgument("Channel depth should be either 1 (GRY), "
- "3 (RGB), or 4 (RGBA)");
+ return errors::InvalidArgument(
+ "Channel depth should be either 1 (GRY), "
+ "3 (RGB), or 4 (RGBA)");
}
}
@@ -709,6 +710,40 @@ REGISTER_OP("NonMaxSuppressionV3")
return Status::OK();
});
+REGISTER_OP("NonMaxSuppressionV4")
+ .Input("boxes: float")
+ .Input("scores: float")
+ .Input("max_output_size: int32")
+ .Input("iou_threshold: float")
+ .Input("score_threshold: float")
+ .Output("selected_indices: int32")
+ .Output("valid_outputs: int32")
+ .Attr("pad_to_max_output_size: bool = false")
+ .SetShapeFn([](InferenceContext* c) {
+ // Get inputs and validate ranks.
+ ShapeHandle boxes;
+ TF_RETURN_IF_ERROR(c->WithRank(c->input(0), 2, &boxes));
+ ShapeHandle scores;
+ TF_RETURN_IF_ERROR(c->WithRank(c->input(1), 1, &scores));
+ ShapeHandle max_output_size;
+ TF_RETURN_IF_ERROR(c->WithRank(c->input(2), 0, &max_output_size));
+ ShapeHandle iou_threshold;
+ TF_RETURN_IF_ERROR(c->WithRank(c->input(3), 0, &iou_threshold));
+ ShapeHandle score_threshold;
+ TF_RETURN_IF_ERROR(c->WithRank(c->input(4), 0, &score_threshold));
+ // The boxes is a 2-D float Tensor of shape [num_boxes, 4].
+ DimensionHandle unused;
+ // The boxes[0] and scores[0] are both num_boxes.
+ TF_RETURN_IF_ERROR(
+ c->Merge(c->Dim(boxes, 0), c->Dim(scores, 0), &unused));
+ // The boxes[1] is 4.
+ TF_RETURN_IF_ERROR(c->WithValue(c->Dim(boxes, 1), 4, &unused));
+
+ c->set_output(0, c->Vector(c->UnknownDim()));
+ c->set_output(1, c->MakeShape({}));
+ return Status::OK();
+ });
+
REGISTER_OP("NonMaxSuppressionWithOverlaps")
.Input("overlaps: float")
.Input("scores: float")
diff --git a/tensorflow/core/ops/math_grad_test.cc b/tensorflow/core/ops/math_grad_test.cc
index da38a6bc24..cfa3a64328 100644
--- a/tensorflow/core/ops/math_grad_test.cc
+++ b/tensorflow/core/ops/math_grad_test.cc
@@ -774,12 +774,40 @@ TEST_F(MathGradTest, ComplexPow) {
};
SymGrad("Pow", x, y, &dx, &dy);
+ // This case failed on Kokoro MacOS:
+ // dx[2] = (-4,6.0398321011234657e-07),
+ // test::AsTensor[2] = (-4,-3.4969110629390343e-07).
+ // dx[2] on linux is close to test::AsTensor[2].
+ // This error hasn't shown up before because
+ // ExpectClose used to check just the magnitude of a complex number, i.e.,
+ // std::abs(complex) = sqrt(real^2 + imag^2).
+ // Now ExpectClose checks the value of each component separately.
+ // Workaround: I set a big tolerance to make the case pass for now.
+ // TODO(penporn): Fix this or file a bug. This is not a precision issue.
+ // Even the most significant digit (or the sign) doesn't match.
test::ExpectClose(
- dx, test::AsTensor<complex64>({g(0.f, 2.f), g(2.f, 2.f), g(-2.f, 2.f)},
- TensorShape({3})));
+ dx,
+ test::AsTensor<complex64>({g(0.f, 2.f), g(2.f, 2.f), g(-2.f, 2.f)},
+ TensorShape({3})),
+ 1e-6f);
+
+ // This case failed on Kokoro MacOS:
+ // dx[2] = (2.7725925445556641,12.56636905670166),
+ // test::AsTensor[2] = (2.7725865840911865,12.566371917724609)
+ // dx[2] on linux is close to test::AsTensor[2].
+ // Default atol = rtol = 5.96046e-07.
+ // Real: diff = 5.96046e-06 > threshold = 2.248633e-06 <- failed
+ // Complex: diff = 2.86102e-06 <= threshold = 8.08618e-06 <- passed
+ // Again, this error hasn't shown up before because ExpectClose used to
+ // check just the magnitude of the complex number. Now it checks each
+ // component separately.
+ // Workaround: Set a larger tolerance for now.
+ // TODO(penporn): See if this is a precision issue or a bug.
test::ExpectClose(
- dy, test::AsTensor<complex64>({h(0.f, 2.f), h(2.f, 2.f), h(-2.f, 2.f)},
- TensorShape({3})));
+ dy,
+ test::AsTensor<complex64>({h(0.f, 2.f), h(2.f, 2.f), h(-2.f, 2.f)},
+ TensorShape({3})),
+ 4.5e-6f);
}
#endif // TENSORFLOW_USE_SYCL
diff --git a/tensorflow/core/ops/math_ops.cc b/tensorflow/core/ops/math_ops.cc
index 386ae9635a..1667c398f4 100644
--- a/tensorflow/core/ops/math_ops.cc
+++ b/tensorflow/core/ops/math_ops.cc
@@ -114,6 +114,7 @@ REGISTER_OP("Cast")
.Output("y: DstT")
.Attr("SrcT: type")
.Attr("DstT: type")
+ .Attr("Truncate: bool = false")
.SetShapeFn(shape_inference::UnchangedShape);
REGISTER_OP("_HostCast")
@@ -121,6 +122,7 @@ REGISTER_OP("_HostCast")
.Output("y: DstT")
.Attr("SrcT: type")
.Attr("DstT: type")
+ .Attr("Truncate: bool = false")
.SetShapeFn(shape_inference::UnchangedShape)
.Doc(R"doc(
Cast x of type SrcT to y of DstT.
diff --git a/tensorflow/core/ops/ops.pbtxt b/tensorflow/core/ops/ops.pbtxt
index 978bb0bbf4..7973be88e0 100644
--- a/tensorflow/core/ops/ops.pbtxt
+++ b/tensorflow/core/ops/ops.pbtxt
@@ -4977,6 +4977,13 @@ op {
name: "DstT"
type: "type"
}
+ attr {
+ name: "Truncate"
+ type: "bool"
+ default_value {
+ b: false
+ }
+ }
}
op {
name: "Ceil"
@@ -17000,6 +17007,44 @@ op {
}
}
op {
+ name: "NonMaxSuppressionV4"
+ input_arg {
+ name: "boxes"
+ type: DT_FLOAT
+ }
+ input_arg {
+ name: "scores"
+ type: DT_FLOAT
+ }
+ input_arg {
+ name: "max_output_size"
+ type: DT_INT32
+ }
+ input_arg {
+ name: "iou_threshold"
+ type: DT_FLOAT
+ }
+ input_arg {
+ name: "score_threshold"
+ type: DT_FLOAT
+ }
+ output_arg {
+ name: "selected_indices"
+ type: DT_INT32
+ }
+ output_arg {
+ name: "valid_outputs"
+ type: DT_INT32
+ }
+ attr {
+ name: "pad_to_max_output_size"
+ type: "bool"
+ default_value {
+ b: false
+ }
+ }
+}
+op {
name: "NonMaxSuppressionWithOverlaps"
input_arg {
name: "overlaps"
diff --git a/tensorflow/core/platform/cloud/BUILD b/tensorflow/core/platform/cloud/BUILD
index 67651349ea..549996aaf8 100644
--- a/tensorflow/core/platform/cloud/BUILD
+++ b/tensorflow/core/platform/cloud/BUILD
@@ -73,6 +73,7 @@ cc_library(
linkstatic = 1, # Needed since alwayslink is broken in bazel b/27630669
visibility = ["//visibility:public"],
deps = [
+ ":compute_engine_metadata_client",
":curl_http_request",
":expiring_lru_cache",
":file_block_cache",
@@ -144,7 +145,7 @@ cc_library(
copts = tf_copts(),
visibility = ["//tensorflow:__subpackages__"],
deps = [
- ":curl_http_request",
+ ":compute_engine_metadata_client",
":oauth_client",
":retrying_utils",
"//tensorflow/core:lib",
@@ -154,6 +155,43 @@ cc_library(
)
cc_library(
+ name = "compute_engine_metadata_client",
+ srcs = [
+ "compute_engine_metadata_client.cc",
+ ],
+ hdrs = [
+ "compute_engine_metadata_client.h",
+ ],
+ copts = tf_copts(),
+ visibility = ["//tensorflow:__subpackages__"],
+ deps = [
+ ":curl_http_request",
+ ":http_request",
+ ":retrying_utils",
+ "//tensorflow/core:lib",
+ "//tensorflow/core:lib_internal",
+ ],
+)
+
+cc_library(
+ name = "compute_engine_zone_provider",
+ srcs = [
+ "compute_engine_zone_provider.cc",
+ ],
+ hdrs = [
+ "compute_engine_zone_provider.h",
+ "zone_provider.h",
+ ],
+ copts = tf_copts(),
+ visibility = ["//tensorflow:__subpackages__"],
+ deps = [
+ ":compute_engine_metadata_client",
+ "//tensorflow/core:lib",
+ "//tensorflow/core:lib_internal",
+ ],
+)
+
+cc_library(
name = "now_seconds_env",
testonly = 1,
hdrs = ["now_seconds_env.h"],
@@ -345,6 +383,34 @@ tf_cc_test(
)
tf_cc_test(
+ name = "compute_engine_metadata_client_test",
+ size = "small",
+ srcs = ["compute_engine_metadata_client_test.cc"],
+ deps = [
+ ":compute_engine_metadata_client",
+ ":http_request_fake",
+ "//tensorflow/core:lib",
+ "//tensorflow/core:lib_internal",
+ "//tensorflow/core:test",
+ "//tensorflow/core:test_main",
+ ],
+)
+
+tf_cc_test(
+ name = "compute_engine_zone_provider_test",
+ size = "small",
+ srcs = ["compute_engine_zone_provider_test.cc"],
+ deps = [
+ ":compute_engine_zone_provider",
+ ":http_request_fake",
+ "//tensorflow/core:lib",
+ "//tensorflow/core:lib_internal",
+ "//tensorflow/core:test",
+ "//tensorflow/core:test_main",
+ ],
+)
+
+tf_cc_test(
name = "retrying_file_system_test",
size = "small",
srcs = ["retrying_file_system_test.cc"],
diff --git a/tensorflow/core/platform/cloud/compute_engine_metadata_client.cc b/tensorflow/core/platform/cloud/compute_engine_metadata_client.cc
new file mode 100644
index 0000000000..f41b83ac34
--- /dev/null
+++ b/tensorflow/core/platform/cloud/compute_engine_metadata_client.cc
@@ -0,0 +1,59 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/core/platform/cloud/compute_engine_metadata_client.h"
+
+#include <utility>
+#include "tensorflow/core/platform/cloud/curl_http_request.h"
+#include "tensorflow/core/platform/cloud/retrying_utils.h"
+
+namespace tensorflow {
+
+namespace {
+
+// The URL to retrieve metadata when running in Google Compute Engine.
+constexpr char kGceMetadataBaseUrl[] = "http://metadata/computeMetadata/v1/";
+// The default initial delay between retries with exponential backoff.
+constexpr int kInitialRetryDelayUsec = 500000; // 0.5 sec
+
+} // namespace
+
+ComputeEngineMetadataClient::ComputeEngineMetadataClient(
+ std::shared_ptr<HttpRequest::Factory> http_request_factory)
+ : ComputeEngineMetadataClient(std::move(http_request_factory),
+ kInitialRetryDelayUsec) {}
+
+ComputeEngineMetadataClient::ComputeEngineMetadataClient(
+ std::shared_ptr<HttpRequest::Factory> http_request_factory,
+ int64 initial_retry_delay_usec)
+ : http_request_factory_(std::move(http_request_factory)),
+ initial_retry_delay_usec_(initial_retry_delay_usec) {}
+
+Status ComputeEngineMetadataClient::GetMetadata(
+ const string& path, std::vector<char>* response_buffer) {
+ const auto get_metadata_from_gce = [path, response_buffer, this]() {
+ std::unique_ptr<HttpRequest> request(http_request_factory_->Create());
+ request->SetUri(kGceMetadataBaseUrl + path);
+ request->AddHeader("Metadata-Flavor", "Google");
+ request->SetResultBuffer(response_buffer);
+ TF_RETURN_IF_ERROR(request->Send());
+ return Status::OK();
+ };
+
+ return RetryingUtils::CallWithRetries(get_metadata_from_gce,
+ initial_retry_delay_usec_);
+}
+
+} // namespace tensorflow
diff --git a/tensorflow/core/platform/cloud/compute_engine_metadata_client.h b/tensorflow/core/platform/cloud/compute_engine_metadata_client.h
new file mode 100644
index 0000000000..534ccf30b2
--- /dev/null
+++ b/tensorflow/core/platform/cloud/compute_engine_metadata_client.h
@@ -0,0 +1,64 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#ifndef TENSORFLOW_CORE_PLATFORM_CLOUD_COMPUTE_ENGINE_METADATA_CLIENT_H_
+#define TENSORFLOW_CORE_PLATFORM_CLOUD_COMPUTE_ENGINE_METADATA_CLIENT_H_
+
+#include "tensorflow/core/lib/core/status.h"
+#include "tensorflow/core/platform/cloud/http_request.h"
+
+namespace tensorflow {
+
+/// \brief A client that accesses to the metadata server running on GCE hosts.
+///
+/// Uses the provided HttpRequest::Factory to make requests to the local
+/// metadata service
+/// (https://cloud.google.com/compute/docs/storing-retrieving-metadata).
+/// Retries on recoverable failures using exponential backoff with the initial
+/// retry wait configurable via initial_retry_delay_usec.
+class ComputeEngineMetadataClient {
+ public:
+ explicit ComputeEngineMetadataClient(
+ std::shared_ptr<HttpRequest::Factory> http_request_factory);
+ ComputeEngineMetadataClient(
+ std::shared_ptr<HttpRequest::Factory> http_request_factory,
+ int64 initial_retry_delay_usec);
+ virtual ~ComputeEngineMetadataClient() {}
+
+ /// \brief Get the metadata value for a given attribute of the metadata
+ /// service.
+ ///
+ /// Given a metadata path relative
+ /// to http://metadata.google.internal/computeMetadata/v1/,
+ /// fills response_buffer with the metadata. Returns OK if the server returns
+ /// the response for the given metadata path successfully.
+ ///
+ /// Example usage:
+ /// To get the zone of an instance:
+ /// compute_engine_metadata_client.GetMetadata(
+ /// "instance/zone", response_buffer);
+ virtual Status GetMetadata(const string& path,
+ std::vector<char>* response_buffer);
+
+ private:
+ std::shared_ptr<HttpRequest::Factory> http_request_factory_;
+ const int64 initial_retry_delay_usec_;
+
+ TF_DISALLOW_COPY_AND_ASSIGN(ComputeEngineMetadataClient);
+};
+
+} // namespace tensorflow
+
+#endif // TENSORFLOW_CORE_PLATFORM_CLOUD_COMPUTE_ENGINE_METADATA_CLIENT_H_
diff --git a/tensorflow/core/platform/cloud/compute_engine_metadata_client_test.cc b/tensorflow/core/platform/cloud/compute_engine_metadata_client_test.cc
new file mode 100644
index 0000000000..4c41ccaa0e
--- /dev/null
+++ b/tensorflow/core/platform/cloud/compute_engine_metadata_client_test.cc
@@ -0,0 +1,68 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/core/platform/cloud/compute_engine_metadata_client.h"
+#include "tensorflow/core/platform/cloud/http_request_fake.h"
+#include "tensorflow/core/platform/test.h"
+
+namespace tensorflow {
+
+TEST(ComputeEngineMetadataClientTest, GetMetadata) {
+ const string example_response = "example response";
+
+ std::vector<HttpRequest*> requests({new FakeHttpRequest(
+ "Uri: http://metadata/computeMetadata/v1/instance/service-accounts"
+ "/default/token\n"
+ "Header Metadata-Flavor: Google\n",
+ example_response)});
+
+ std::shared_ptr<HttpRequest::Factory> http_factory =
+ std::make_shared<FakeHttpRequestFactory>(&requests);
+ ComputeEngineMetadataClient client(http_factory, 0);
+
+ std::vector<char> result;
+ TF_EXPECT_OK(
+ client.GetMetadata("instance/service-accounts/default/token", &result));
+ std::vector<char> expected(example_response.begin(), example_response.end());
+ EXPECT_EQ(expected, result);
+}
+
+TEST(ComputeEngineMetadataClientTest, RetryOnFailure) {
+ const string example_response = "example response";
+
+ std::vector<HttpRequest*> requests(
+ {new FakeHttpRequest(
+ "Uri: http://metadata/computeMetadata/v1/instance/service-accounts"
+ "/default/token\n"
+ "Header Metadata-Flavor: Google\n",
+ "", errors::Unavailable("503"), 503),
+ new FakeHttpRequest(
+ "Uri: http://metadata/computeMetadata/v1/instance/service-accounts"
+ "/default/token\n"
+ "Header Metadata-Flavor: Google\n",
+ example_response)});
+
+ std::shared_ptr<HttpRequest::Factory> http_factory =
+ std::make_shared<FakeHttpRequestFactory>(&requests);
+ ComputeEngineMetadataClient client(http_factory, 0);
+
+ std::vector<char> result;
+ TF_EXPECT_OK(
+ client.GetMetadata("instance/service-accounts/default/token", &result));
+ std::vector<char> expected(example_response.begin(), example_response.end());
+ EXPECT_EQ(expected, result);
+}
+
+} // namespace tensorflow
diff --git a/tensorflow/core/platform/cloud/compute_engine_zone_provider.cc b/tensorflow/core/platform/cloud/compute_engine_zone_provider.cc
new file mode 100644
index 0000000000..dacf56187c
--- /dev/null
+++ b/tensorflow/core/platform/cloud/compute_engine_zone_provider.cc
@@ -0,0 +1,53 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/core/platform/cloud/compute_engine_zone_provider.h"
+
+#include <utility>
+#include "tensorflow/core/lib/strings/str_util.h"
+namespace tensorflow {
+
+namespace {
+constexpr char kGceMetadataZonePath[] = "instance/zone";
+} // namespace
+
+ComputeEngineZoneProvider::ComputeEngineZoneProvider(
+ std::shared_ptr<ComputeEngineMetadataClient> google_metadata_client)
+ : google_metadata_client_(std::move(google_metadata_client)) {}
+
+Status ComputeEngineZoneProvider::GetZone(string* zone) {
+ if (!cached_zone.empty()) {
+ *zone = cached_zone;
+ return Status::OK();
+ }
+ std::vector<char> response_buffer;
+ TF_RETURN_IF_ERROR(google_metadata_client_->GetMetadata(kGceMetadataZonePath,
+ &response_buffer));
+ StringPiece location(&response_buffer[0], response_buffer.size());
+
+ std::vector<string> elems = str_util::Split(location, "/");
+ if (elems.size() == 4) {
+ cached_zone = elems.back();
+ *zone = cached_zone;
+ } else {
+ LOG(ERROR) << "Failed to parse the zone name from location: "
+ << location.ToString();
+ }
+
+ return Status::OK();
+}
+ComputeEngineZoneProvider::~ComputeEngineZoneProvider() {}
+
+} // namespace tensorflow
diff --git a/tensorflow/core/platform/cloud/compute_engine_zone_provider.h b/tensorflow/core/platform/cloud/compute_engine_zone_provider.h
new file mode 100644
index 0000000000..614b688e6f
--- /dev/null
+++ b/tensorflow/core/platform/cloud/compute_engine_zone_provider.h
@@ -0,0 +1,40 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#ifndef TENSORFLOW_CORE_PLATFORM_CLOUD_COMPUTE_ENGINE_ZONE_PROVIDER_H_
+#define TENSORFLOW_CORE_PLATFORM_CLOUD_COMPUTE_ENGINE_ZONE_PROVIDER_H_
+
+#include "tensorflow/core/platform/cloud/compute_engine_metadata_client.h"
+#include "tensorflow/core/platform/cloud/zone_provider.h"
+
+namespace tensorflow {
+
+class ComputeEngineZoneProvider : public ZoneProvider {
+ public:
+ explicit ComputeEngineZoneProvider(
+ std::shared_ptr<ComputeEngineMetadataClient> google_metadata_client);
+ virtual ~ComputeEngineZoneProvider();
+
+ Status GetZone(string* zone) override;
+
+ private:
+ std::shared_ptr<ComputeEngineMetadataClient> google_metadata_client_;
+ string cached_zone;
+ TF_DISALLOW_COPY_AND_ASSIGN(ComputeEngineZoneProvider);
+};
+
+} // namespace tensorflow
+
+#endif // TENSORFLOW_CORE_PLATFORM_CLOUD_COMPUTE_ENGINE_ZONE_PROVIDER_H_
diff --git a/tensorflow/core/platform/cloud/compute_engine_zone_provider_test.cc b/tensorflow/core/platform/cloud/compute_engine_zone_provider_test.cc
new file mode 100644
index 0000000000..f7477eca23
--- /dev/null
+++ b/tensorflow/core/platform/cloud/compute_engine_zone_provider_test.cc
@@ -0,0 +1,69 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/core/platform/cloud/compute_engine_zone_provider.h"
+#include "tensorflow/core/platform/cloud/http_request_fake.h"
+#include "tensorflow/core/platform/test.h"
+
+namespace tensorflow {
+
+class ComputeEngineZoneProviderTest : public ::testing::Test {
+ protected:
+ void SetUp() override {}
+
+ void TearDown() override {}
+};
+
+TEST_F(ComputeEngineZoneProviderTest, GetZone) {
+ std::vector<HttpRequest*> requests({new FakeHttpRequest(
+ "Uri: http://metadata/computeMetadata/v1/instance/zone\n"
+ "Header Metadata-Flavor: Google\n",
+ "projects/123456789/zones/us-west1-b")});
+
+ auto httpRequestFactory = std::make_shared<FakeHttpRequestFactory>(&requests);
+
+ auto metadata_client =
+ std::make_shared<ComputeEngineMetadataClient>(httpRequestFactory, 0);
+
+ ComputeEngineZoneProvider provider(metadata_client);
+
+ string zone;
+
+ TF_EXPECT_OK(provider.GetZone(&zone));
+ EXPECT_EQ("us-west1-b", zone);
+ // Test caching, should be no further requests
+ TF_EXPECT_OK(provider.GetZone(&zone));
+}
+
+TEST_F(ComputeEngineZoneProviderTest, InvalidZoneString) {
+ std::vector<HttpRequest*> requests({new FakeHttpRequest(
+ "Uri: http://metadata/computeMetadata/v1/instance/zone\n"
+ "Header Metadata-Flavor: Google\n",
+ "invalidresponse")});
+
+ auto httpRequestFactory = std::make_shared<FakeHttpRequestFactory>(&requests);
+
+ auto metadata_client =
+ std::make_shared<ComputeEngineMetadataClient>(httpRequestFactory, 0);
+
+ ComputeEngineZoneProvider provider(metadata_client);
+
+ string zone;
+
+ TF_EXPECT_OK(provider.GetZone(&zone));
+ EXPECT_EQ("", zone);
+}
+
+} // namespace tensorflow
diff --git a/tensorflow/core/platform/cloud/gcs_file_system.cc b/tensorflow/core/platform/cloud/gcs_file_system.cc
index aa35e8a116..2e8d13acd5 100644
--- a/tensorflow/core/platform/cloud/gcs_file_system.cc
+++ b/tensorflow/core/platform/cloud/gcs_file_system.cc
@@ -605,13 +605,18 @@ bool StringPieceIdentity(StringPiece str, StringPiece* value) {
} // namespace
-GcsFileSystem::GcsFileSystem()
- : auth_provider_(new GoogleAuthProvider()),
- http_request_factory_(new CurlHttpRequest::Factory()) {
+GcsFileSystem::GcsFileSystem() {
uint64 value;
size_t block_size = kDefaultBlockSize;
size_t max_bytes = kDefaultMaxCacheSize;
uint64 max_staleness = kDefaultMaxStaleness;
+
+ http_request_factory_ = std::make_shared<CurlHttpRequest::Factory>();
+ compute_engine_metadata_client_ =
+ std::make_shared<ComputeEngineMetadataClient>(http_request_factory_);
+ auth_provider_ = std::unique_ptr<AuthProvider>(
+ new GoogleAuthProvider(compute_engine_metadata_client_));
+
// Apply the sys env override for the readahead buffer size if it's provided.
if (GetEnvVar(kReadaheadBufferSize, strings::safe_strtou64, &value)) {
block_size = value;
diff --git a/tensorflow/core/platform/cloud/gcs_file_system.h b/tensorflow/core/platform/cloud/gcs_file_system.h
index 74768c98b5..a0372286f5 100644
--- a/tensorflow/core/platform/cloud/gcs_file_system.h
+++ b/tensorflow/core/platform/cloud/gcs_file_system.h
@@ -22,6 +22,7 @@ limitations under the License.
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/platform/cloud/auth_provider.h"
+#include "tensorflow/core/platform/cloud/compute_engine_metadata_client.h"
#include "tensorflow/core/platform/cloud/expiring_lru_cache.h"
#include "tensorflow/core/platform/cloud/file_block_cache.h"
#include "tensorflow/core/platform/cloud/gcs_dns_cache.h"
@@ -275,12 +276,13 @@ class GcsFileSystem : public FileSystem {
mutex mu_;
std::unique_ptr<AuthProvider> auth_provider_ GUARDED_BY(mu_);
- std::unique_ptr<HttpRequest::Factory> http_request_factory_;
+ std::shared_ptr<HttpRequest::Factory> http_request_factory_;
// block_cache_lock_ protects the file_block_cache_ pointer (Note that
// FileBlockCache instances are themselves threadsafe).
mutex block_cache_lock_;
std::unique_ptr<FileBlockCache> file_block_cache_
GUARDED_BY(block_cache_lock_);
+ std::shared_ptr<ComputeEngineMetadataClient> compute_engine_metadata_client_;
std::unique_ptr<GcsDnsCache> dns_cache_;
GcsThrottle throttle_;
diff --git a/tensorflow/core/platform/cloud/gcs_throttle_test.cc b/tensorflow/core/platform/cloud/gcs_throttle_test.cc
index 57193ac405..8f962b92b8 100644
--- a/tensorflow/core/platform/cloud/gcs_throttle_test.cc
+++ b/tensorflow/core/platform/cloud/gcs_throttle_test.cc
@@ -24,14 +24,14 @@ namespace {
class TestTime : public EnvTime {
public:
- uint64 NowMicros() override { return now_; }
+ uint64 NowNanos() override { return now_micros_ * kMicrosToNanos; }
- void SetTime(uint64 now_micros) { now_ = now_micros; }
+ void SetTime(uint64 now_micros) { now_micros_ = now_micros; }
- void AdvanceSeconds(int64 secs) { now_ += secs * 1000000L; }
+ void AdvanceSeconds(int64 secs) { now_micros_ += secs * kSecondsToMicros; }
private:
- uint64 now_ = 1234567890000000ULL;
+ uint64 now_micros_ = 1234567890000000ULL;
};
class GcsThrottleTest : public ::testing::Test {
diff --git a/tensorflow/core/platform/cloud/google_auth_provider.cc b/tensorflow/core/platform/cloud/google_auth_provider.cc
index 7e39b63e3e..6ffe51e897 100644
--- a/tensorflow/core/platform/cloud/google_auth_provider.cc
+++ b/tensorflow/core/platform/cloud/google_auth_provider.cc
@@ -21,11 +21,11 @@ limitations under the License.
#include <sys/types.h>
#endif
#include <fstream>
+#include <utility>
#include "include/json/json.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/io/path.h"
#include "tensorflow/core/lib/strings/base64.h"
-#include "tensorflow/core/platform/cloud/curl_http_request.h"
#include "tensorflow/core/platform/cloud/retrying_utils.h"
#include "tensorflow/core/platform/env.h"
@@ -63,16 +63,11 @@ constexpr char kOAuthV4Url[] = "https://www.googleapis.com/oauth2/v4/token";
// The URL to retrieve the auth bearer token when running in Google Compute
// Engine.
-constexpr char kGceTokenUrl[] =
- "http://metadata/computeMetadata/v1/instance/service-accounts/default/"
- "token";
+constexpr char kGceTokenPath[] = "instance/service-accounts/default/token";
// The authentication token scope to request.
constexpr char kOAuthScope[] = "https://www.googleapis.com/auth/cloud-platform";
-// The default initial delay between retries with exponential backoff.
-constexpr int kInitialRetryDelayUsec = 500000; // 0.5 sec
-
/// Returns whether the given path points to a readable file.
bool IsFile(const string& filename) {
std::ifstream fstream(filename.c_str());
@@ -121,20 +116,20 @@ Status GetWellKnownFileName(string* filename) {
} // namespace
-GoogleAuthProvider::GoogleAuthProvider()
- : GoogleAuthProvider(
- std::unique_ptr<OAuthClient>(new OAuthClient()),
- std::unique_ptr<HttpRequest::Factory>(new CurlHttpRequest::Factory()),
- Env::Default(), kInitialRetryDelayUsec) {}
+GoogleAuthProvider::GoogleAuthProvider(
+ std::shared_ptr<ComputeEngineMetadataClient> compute_engine_metadata_client)
+ : GoogleAuthProvider(std::unique_ptr<OAuthClient>(new OAuthClient()),
+ std::move(compute_engine_metadata_client),
+ Env::Default()) {}
GoogleAuthProvider::GoogleAuthProvider(
std::unique_ptr<OAuthClient> oauth_client,
- std::unique_ptr<HttpRequest::Factory> http_request_factory, Env* env,
- int64 initial_retry_delay_usec)
+ std::shared_ptr<ComputeEngineMetadataClient> compute_engine_metadata_client,
+ Env* env)
: oauth_client_(std::move(oauth_client)),
- http_request_factory_(std::move(http_request_factory)),
- env_(env),
- initial_retry_delay_usec_(initial_retry_delay_usec) {}
+ compute_engine_metadata_client_(
+ std::move(compute_engine_metadata_client)),
+ env_(env) {}
Status GoogleAuthProvider::GetToken(string* t) {
mutex_lock lock(mu_);
@@ -207,24 +202,19 @@ Status GoogleAuthProvider::GetTokenFromFiles() {
}
Status GoogleAuthProvider::GetTokenFromGce() {
- const auto get_token_from_gce = [this]() {
- std::unique_ptr<HttpRequest> request(http_request_factory_->Create());
- std::vector<char> response_buffer;
- const uint64 request_timestamp_sec = env_->NowSeconds();
- request->SetUri(kGceTokenUrl);
- request->AddHeader("Metadata-Flavor", "Google");
- request->SetResultBuffer(&response_buffer);
- TF_RETURN_IF_ERROR(request->Send());
- StringPiece response =
- StringPiece(&response_buffer[0], response_buffer.size());
-
- TF_RETURN_IF_ERROR(oauth_client_->ParseOAuthResponse(
- response, request_timestamp_sec, &current_token_,
- &expiration_timestamp_sec_));
- return Status::OK();
- };
- return RetryingUtils::CallWithRetries(get_token_from_gce,
- initial_retry_delay_usec_);
+ std::vector<char> response_buffer;
+ const uint64 request_timestamp_sec = env_->NowSeconds();
+
+ TF_RETURN_IF_ERROR(compute_engine_metadata_client_->GetMetadata(
+ kGceTokenPath, &response_buffer));
+ StringPiece response =
+ StringPiece(&response_buffer[0], response_buffer.size());
+
+ TF_RETURN_IF_ERROR(oauth_client_->ParseOAuthResponse(
+ response, request_timestamp_sec, &current_token_,
+ &expiration_timestamp_sec_));
+
+ return Status::OK();
}
Status GoogleAuthProvider::GetTokenForTesting() {
diff --git a/tensorflow/core/platform/cloud/google_auth_provider.h b/tensorflow/core/platform/cloud/google_auth_provider.h
index 00da25a959..58a785fd60 100644
--- a/tensorflow/core/platform/cloud/google_auth_provider.h
+++ b/tensorflow/core/platform/cloud/google_auth_provider.h
@@ -18,6 +18,7 @@ limitations under the License.
#include <memory>
#include "tensorflow/core/platform/cloud/auth_provider.h"
+#include "tensorflow/core/platform/cloud/compute_engine_metadata_client.h"
#include "tensorflow/core/platform/cloud/oauth_client.h"
#include "tensorflow/core/platform/mutex.h"
#include "tensorflow/core/platform/thread_annotations.h"
@@ -27,11 +28,12 @@ namespace tensorflow {
/// Implementation based on Google Application Default Credentials.
class GoogleAuthProvider : public AuthProvider {
public:
- GoogleAuthProvider();
- explicit GoogleAuthProvider(
- std::unique_ptr<OAuthClient> oauth_client,
- std::unique_ptr<HttpRequest::Factory> http_request_factory, Env* env,
- int64 initial_retry_delay_usec);
+ GoogleAuthProvider(std::shared_ptr<ComputeEngineMetadataClient>
+ compute_engine_metadata_client);
+ explicit GoogleAuthProvider(std::unique_ptr<OAuthClient> oauth_client,
+ std::shared_ptr<ComputeEngineMetadataClient>
+ compute_engine_metadata_client,
+ Env* env);
virtual ~GoogleAuthProvider() {}
/// \brief Returns the short-term authentication bearer token.
@@ -53,13 +55,11 @@ class GoogleAuthProvider : public AuthProvider {
Status GetTokenForTesting() EXCLUSIVE_LOCKS_REQUIRED(mu_);
std::unique_ptr<OAuthClient> oauth_client_;
- std::unique_ptr<HttpRequest::Factory> http_request_factory_;
+ std::shared_ptr<ComputeEngineMetadataClient> compute_engine_metadata_client_;
Env* env_;
mutex mu_;
string current_token_ GUARDED_BY(mu_);
uint64 expiration_timestamp_sec_ GUARDED_BY(mu_) = 0;
- // The initial delay for exponential backoffs when retrying failed calls.
- const int64 initial_retry_delay_usec_;
TF_DISALLOW_COPY_AND_ASSIGN(GoogleAuthProvider);
};
diff --git a/tensorflow/core/platform/cloud/google_auth_provider_test.cc b/tensorflow/core/platform/cloud/google_auth_provider_test.cc
index 4281c6c737..07b88a880f 100644
--- a/tensorflow/core/platform/cloud/google_auth_provider_test.cc
+++ b/tensorflow/core/platform/cloud/google_auth_provider_test.cc
@@ -90,10 +90,13 @@ TEST_F(GoogleAuthProviderTest, EnvironmentVariable_Caching) {
std::vector<HttpRequest*> requests;
FakeEnv env;
+
+ std::shared_ptr<HttpRequest::Factory> fakeHttpRequestFactory =
+ std::make_shared<FakeHttpRequestFactory>(&requests);
+ auto metadataClient =
+ std::make_shared<ComputeEngineMetadataClient>(fakeHttpRequestFactory, 0);
GoogleAuthProvider provider(std::unique_ptr<OAuthClient>(oauth_client),
- std::unique_ptr<HttpRequest::Factory>(
- new FakeHttpRequestFactory(&requests)),
- &env, 0);
+ metadataClient, &env);
oauth_client->return_token = "fake-token";
oauth_client->return_expiration_timestamp = env.NowSeconds() + 3600;
@@ -124,10 +127,13 @@ TEST_F(GoogleAuthProviderTest, GCloudRefreshToken) {
std::vector<HttpRequest*> requests;
FakeEnv env;
+ std::shared_ptr<HttpRequest::Factory> fakeHttpRequestFactory =
+ std::make_shared<FakeHttpRequestFactory>(&requests);
+ auto metadataClient =
+ std::make_shared<ComputeEngineMetadataClient>(fakeHttpRequestFactory, 0);
+
GoogleAuthProvider provider(std::unique_ptr<OAuthClient>(oauth_client),
- std::unique_ptr<HttpRequest::Factory>(
- new FakeHttpRequestFactory(&requests)),
- &env, 0);
+ metadataClient, &env);
oauth_client->return_token = "fake-token";
oauth_client->return_expiration_timestamp = env.NowSeconds() + 3600;
@@ -170,10 +176,12 @@ TEST_F(GoogleAuthProviderTest, RunningOnGCE) {
})")});
FakeEnv env;
+ std::shared_ptr<HttpRequest::Factory> fakeHttpRequestFactory =
+ std::make_shared<FakeHttpRequestFactory>(&requests);
+ auto metadataClient =
+ std::make_shared<ComputeEngineMetadataClient>(fakeHttpRequestFactory, 0);
GoogleAuthProvider provider(std::unique_ptr<OAuthClient>(oauth_client),
- std::unique_ptr<HttpRequest::Factory>(
- new FakeHttpRequestFactory(&requests)),
- &env, 0);
+ metadataClient, &env);
string token;
TF_EXPECT_OK(provider.GetToken(&token));
@@ -196,10 +204,12 @@ TEST_F(GoogleAuthProviderTest, OverrideForTesting) {
auto oauth_client = new FakeOAuthClient;
std::vector<HttpRequest*> empty_requests;
FakeEnv env;
+ std::shared_ptr<HttpRequest::Factory> fakeHttpRequestFactory =
+ std::make_shared<FakeHttpRequestFactory>(&empty_requests);
+ auto metadataClient =
+ std::make_shared<ComputeEngineMetadataClient>(fakeHttpRequestFactory, 0);
GoogleAuthProvider provider(std::unique_ptr<OAuthClient>(oauth_client),
- std::unique_ptr<HttpRequest::Factory>(
- new FakeHttpRequestFactory(&empty_requests)),
- &env, 0);
+ metadataClient, &env);
string token;
TF_EXPECT_OK(provider.GetToken(&token));
@@ -216,10 +226,12 @@ TEST_F(GoogleAuthProviderTest, NothingAvailable) {
"", errors::NotFound("404"), 404)});
FakeEnv env;
+ std::shared_ptr<HttpRequest::Factory> fakeHttpRequestFactory =
+ std::make_shared<FakeHttpRequestFactory>(&requests);
+ auto metadataClient =
+ std::make_shared<ComputeEngineMetadataClient>(fakeHttpRequestFactory, 0);
GoogleAuthProvider provider(std::unique_ptr<OAuthClient>(oauth_client),
- std::unique_ptr<HttpRequest::Factory>(
- new FakeHttpRequestFactory(&requests)),
- &env, 0);
+ metadataClient, &env);
string token;
TF_EXPECT_OK(provider.GetToken(&token));
diff --git a/tensorflow/core/platform/cloud/zone_provider.h b/tensorflow/core/platform/cloud/zone_provider.h
new file mode 100644
index 0000000000..421b6a7e1a
--- /dev/null
+++ b/tensorflow/core/platform/cloud/zone_provider.h
@@ -0,0 +1,48 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#ifndef TENSORFLOW_CORE_PLATFORM_CLOUD_ZONE_PROVIDER_H_
+#define TENSORFLOW_CORE_PLATFORM_CLOUD_ZONE_PROVIDER_H_
+
+#include <string>
+#include "tensorflow/core/lib/core/errors.h"
+#include "tensorflow/core/lib/core/status.h"
+
+namespace tensorflow {
+
+/// Interface for a provider of cloud instance zone
+class ZoneProvider {
+ public:
+ virtual ~ZoneProvider() {}
+
+ /// \brief Gets the zone of the Cloud instance and set the result in `zone`.
+ /// Returns OK if success.
+ ///
+ /// Returns an empty string in the case where the zone does not match the
+ /// expected format
+ /// Safe for concurrent use by multiple threads.
+ virtual Status GetZone(string* zone) = 0;
+
+ static Status GetZone(ZoneProvider* provider, string* zone) {
+ if (!provider) {
+ return errors::Internal("Zone provider is required.");
+ }
+ return provider->GetZone(zone);
+ }
+};
+
+} // namespace tensorflow
+
+#endif // TENSORFLOW_CORE_PLATFORM_CLOUD_ZONE_PROVIDER_H_
diff --git a/tensorflow/core/platform/default/build_config.bzl b/tensorflow/core/platform/default/build_config.bzl
index 9f6bc70f04..e15aae5488 100644
--- a/tensorflow/core/platform/default/build_config.bzl
+++ b/tensorflow/core/platform/default/build_config.bzl
@@ -707,4 +707,5 @@ def tf_additional_binary_deps():
[
"//third_party/mkl:intel_binary_blob",
],
- ) \ No newline at end of file
+ )
+ \ No newline at end of file
diff --git a/tensorflow/core/platform/default/build_config_root.bzl b/tensorflow/core/platform/default/build_config_root.bzl
index 09029a4b25..3a012c23fd 100644
--- a/tensorflow/core/platform/default/build_config_root.bzl
+++ b/tensorflow/core/platform/default/build_config_root.bzl
@@ -58,3 +58,9 @@ def if_static(extra_deps, otherwise=[]):
str(Label("//tensorflow:framework_shared_object")): otherwise,
"//conditions:default": extra_deps,
})
+
+def if_dynamic_kernels(extra_deps, otherwise=[]):
+ return select({
+ str(Label("//tensorflow:dynamic_loaded_kernels")): extra_deps,
+ "//conditions:default": otherwise,
+ })
diff --git a/tensorflow/core/platform/env_test.cc b/tensorflow/core/platform/env_test.cc
index c461a40086..305a9a682f 100644
--- a/tensorflow/core/platform/env_test.cc
+++ b/tensorflow/core/platform/env_test.cc
@@ -86,7 +86,7 @@ TEST_F(DefaultEnvTest, IncompleteReadOutOfRange) {
TEST_F(DefaultEnvTest, ReadFileToString) {
for (const int length : {0, 1, 1212, 2553, 4928, 8196, 9000, (1 << 20) - 1,
- 1 << 20, (1 << 20) + 1}) {
+ 1 << 20, (1 << 20) + 1, (256 << 20) + 100}) {
const string filename = strings::StrCat(BaseDir(), "/bar/..//file", length);
// Write a file with the given length
diff --git a/tensorflow/core/platform/env_time.h b/tensorflow/core/platform/env_time.h
index 23dbedd60d..b4756ed209 100644
--- a/tensorflow/core/platform/env_time.h
+++ b/tensorflow/core/platform/env_time.h
@@ -25,6 +25,13 @@ namespace tensorflow {
/// access timer related operations.
class EnvTime {
public:
+ static constexpr uint64 kMicrosToNanos = 1000ULL;
+ static constexpr uint64 kMillisToMicros = 1000ULL;
+ static constexpr uint64 kMillisToNanos = 1000ULL * 1000ULL;
+ static constexpr uint64 kSecondsToMillis = 1000ULL;
+ static constexpr uint64 kSecondsToMicros = 1000ULL * 1000ULL;
+ static constexpr uint64 kSecondsToNanos = 1000ULL * 1000ULL * 1000ULL;
+
EnvTime();
virtual ~EnvTime() = default;
@@ -34,11 +41,14 @@ class EnvTime {
/// The result of Default() belongs to this library and must never be deleted.
static EnvTime* Default();
+ /// \brief Returns the number of nano-seconds since the Unix epoch.
+ virtual uint64 NowNanos() = 0;
+
/// \brief Returns the number of micro-seconds since the Unix epoch.
- virtual uint64 NowMicros() = 0;
+ virtual uint64 NowMicros() { return NowNanos() / kMicrosToNanos; }
/// \brief Returns the number of seconds since the Unix epoch.
- virtual uint64 NowSeconds() { return NowMicros() / 1000000L; }
+ virtual uint64 NowSeconds() { return NowNanos() / kSecondsToNanos; }
};
} // namespace tensorflow
diff --git a/tensorflow/core/platform/gif.h b/tensorflow/core/platform/gif.h
index ab095a35c9..61b9fbbcb2 100644
--- a/tensorflow/core/platform/gif.h
+++ b/tensorflow/core/platform/gif.h
@@ -18,10 +18,10 @@ limitations under the License.
#include "tensorflow/core/platform/platform.h"
-#if defined(PLATFORM_GOOGLE)
+#if defined(PLATFORM_GOOGLE) && !defined(IS_MOBILE_PLATFORM)
#include "tensorflow/core/platform/google/build_config/gif.h"
#elif defined(PLATFORM_POSIX) || defined(PLATFORM_WINDOWS) || \
- defined(PLATFORM_POSIX_ANDROID)
+ defined(PLATFORM_POSIX_ANDROID) || defined(IS_MOBILE_PLATFORM)
#include <gif_lib.h>
#else
#error Define the appropriate PLATFORM_<foo> macro for this platform
diff --git a/tensorflow/core/platform/jpeg.h b/tensorflow/core/platform/jpeg.h
index 1b5e633f0a..f98ddb8c98 100644
--- a/tensorflow/core/platform/jpeg.h
+++ b/tensorflow/core/platform/jpeg.h
@@ -18,10 +18,10 @@ limitations under the License.
#include "tensorflow/core/platform/platform.h"
-#if defined(PLATFORM_GOOGLE)
+#if defined(PLATFORM_GOOGLE) && !defined(IS_MOBILE_PLATFORM)
#include "tensorflow/core/platform/google/build_config/jpeg.h"
#elif defined(PLATFORM_POSIX) || defined(PLATFORM_WINDOWS) || \
- defined(PLATFORM_POSIX_ANDROID)
+ defined(PLATFORM_POSIX_ANDROID) || defined(IS_MOBILE_PLATFORM)
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
diff --git a/tensorflow/core/platform/png.h b/tensorflow/core/platform/png.h
index dad18d7219..b110d63aba 100644
--- a/tensorflow/core/platform/png.h
+++ b/tensorflow/core/platform/png.h
@@ -18,10 +18,10 @@ limitations under the License.
#include "tensorflow/core/platform/platform.h"
-#if defined(PLATFORM_GOOGLE)
+#if defined(PLATFORM_GOOGLE) && !defined(IS_MOBILE_PLATFORM)
#include "tensorflow/core/platform/google/build_config/png.h"
#elif defined(PLATFORM_POSIX) || defined(PLATFORM_WINDOWS) || \
- defined(PLATFORM_POSIX_ANDROID)
+ defined(PLATFORM_POSIX_ANDROID) || defined(IS_MOBILE_PLATFORM)
#include <png.h>
#else
#error Define the appropriate PLATFORM_<foo> macro for this platform
diff --git a/tensorflow/core/platform/posix/env_time.cc b/tensorflow/core/platform/posix/env_time.cc
index 341c585a9e..59a67b17aa 100644
--- a/tensorflow/core/platform/posix/env_time.cc
+++ b/tensorflow/core/platform/posix/env_time.cc
@@ -26,10 +26,11 @@ class PosixEnvTime : public EnvTime {
public:
PosixEnvTime() {}
- uint64 NowMicros() override {
- struct timeval tv;
- gettimeofday(&tv, nullptr);
- return static_cast<uint64>(tv.tv_sec) * 1000000 + tv.tv_usec;
+ uint64 NowNanos() override {
+ struct timespec ts;
+ clock_gettime(CLOCK_REALTIME, &ts);
+ return (static_cast<uint64>(ts.tv_sec) * kSecondsToNanos +
+ static_cast<uint64>(ts.tv_nsec));
}
};
diff --git a/tensorflow/core/platform/profile_utils/cpu_utils.cc b/tensorflow/core/platform/profile_utils/cpu_utils.cc
index b0136b52f4..664412565f 100644
--- a/tensorflow/core/platform/profile_utils/cpu_utils.cc
+++ b/tensorflow/core/platform/profile_utils/cpu_utils.cc
@@ -19,6 +19,10 @@ limitations under the License.
#include <limits>
#include <mutex>
+#if defined(_WIN32)
+#include <windows.h>
+#endif
+
#include "tensorflow/core/platform/logging.h"
#include "tensorflow/core/platform/profile_utils/android_armv7a_cpu_utils_helper.h"
@@ -110,6 +114,10 @@ static ICpuUtilsHelper* cpu_utils_helper_instance_ = nullptr;
return INVALID_FREQUENCY;
}
return freq_hz;
+#elif defined(_WIN32)
+ LARGE_INTEGER freq;
+ QueryPerformanceFrequency(&freq);
+ return freq.QuadPart;
#else
// TODO(satok): Support other OS if needed
// Return INVALID_FREQUENCY on unsupported OS
diff --git a/tensorflow/core/platform/profile_utils/cpu_utils.h b/tensorflow/core/platform/profile_utils/cpu_utils.h
index 7b580c8bf6..8f06290303 100644
--- a/tensorflow/core/platform/profile_utils/cpu_utils.h
+++ b/tensorflow/core/platform/profile_utils/cpu_utils.h
@@ -28,6 +28,10 @@ limitations under the License.
#include <sys/time.h>
#endif
+#if defined(_WIN32)
+#include <intrin.h>
+#endif
+
namespace tensorflow {
namespace profile_utils {
@@ -55,6 +59,9 @@ class CpuUtils {
#if defined(__ANDROID__)
return GetCpuUtilsHelperSingletonInstance().GetCurrentClockCycle();
// ----------------------------------------------------------------
+#elif defined(_WIN32)
+ return __rdtsc();
+// ----------------------------------------------------------------
#elif defined(__x86_64__) || defined(__amd64__)
uint64_t high, low;
__asm__ volatile("rdtsc" : "=a"(low), "=d"(high));
diff --git a/tensorflow/core/platform/windows/env_time.cc b/tensorflow/core/platform/windows/env_time.cc
index 16cc9dc675..b1713f695c 100644
--- a/tensorflow/core/platform/windows/env_time.cc
+++ b/tensorflow/core/platform/windows/env_time.cc
@@ -19,6 +19,10 @@ limitations under the License.
#include <windows.h>
#include <chrono>
+using std::chrono::duration_cast;
+using std::chrono::nanoseconds;
+using std::chrono::system_clock;
+
namespace tensorflow {
namespace {
@@ -38,18 +42,17 @@ class WindowsEnvTime : public EnvTime {
}
}
- uint64 NowMicros() override {
+ uint64 NowNanos() {
if (GetSystemTimePreciseAsFileTime_ != NULL) {
// GetSystemTimePreciseAsFileTime function is only available in latest
// versions of Windows, so we need to check for its existence here.
- // All std::chrono clocks on Windows proved to return
- // values that may repeat, which is not good enough for some uses.
+ // All std::chrono clocks on Windows proved to return values that may
+ // repeat, which is not good enough for some uses.
constexpr int64_t kUnixEpochStartTicks = 116444736000000000i64;
- constexpr int64_t kFtToMicroSec = 10;
- // This interface needs to return system time and not
- // just any microseconds because it is often used as an argument
- // to TimedWait() on condition variable
+ // This interface needs to return system time and not just any time
+ // because it is often used as an argument to TimedWait() on condition
+ // variable.
FILETIME system_time;
GetSystemTimePreciseAsFileTime_(&system_time);
@@ -58,12 +61,12 @@ class WindowsEnvTime : public EnvTime {
li.HighPart = system_time.dwHighDateTime;
// Subtract unix epoch start
li.QuadPart -= kUnixEpochStartTicks;
- // Convert to microsecs
- li.QuadPart /= kFtToMicroSec;
+
+ constexpr int64_t kFtToNanoSec = 100;
+ li.QuadPart *= kFtToNanoSec;
return li.QuadPart;
}
- using namespace std::chrono;
- return duration_cast<microseconds>(system_clock::now().time_since_epoch())
+ return duration_cast<nanoseconds>(system_clock::now().time_since_epoch())
.count();
}
diff --git a/tensorflow/core/protobuf/config.proto b/tensorflow/core/protobuf/config.proto
index d701ce8e12..da3a99565e 100644
--- a/tensorflow/core/protobuf/config.proto
+++ b/tensorflow/core/protobuf/config.proto
@@ -393,6 +393,10 @@ message ConfigProto {
// Whether the client will format templated errors. For example, the string:
// "The node was defined on ^^node:Foo:${file}:${line}^^".
bool client_handles_error_formatting = 2;
+
+ // Which executor to use, the default executor will be used
+ // if it is an empty string or "DEFAULT"
+ string executor_type = 3;
};
Experimental experimental = 16;
diff --git a/tensorflow/core/util/equal_graph_def_test.cc b/tensorflow/core/util/equal_graph_def_test.cc
index c54540332e..77ca8eaec3 100644
--- a/tensorflow/core/util/equal_graph_def_test.cc
+++ b/tensorflow/core/util/equal_graph_def_test.cc
@@ -85,7 +85,7 @@ TEST_F(EqualGraphDefTest, NoMatch) {
Input(e_.opts().WithName("A"));
Input(a_.opts().WithName("B"));
EXPECT_FALSE(Match());
- EXPECT_EQ("Did not find expected node 'A = Input[]()'", diff_);
+ EXPECT_EQ("Did not find expected node '{{node A}} = Input[]()'", diff_);
}
TEST_F(EqualGraphDefTest, MissingNode) {
@@ -93,7 +93,7 @@ TEST_F(EqualGraphDefTest, MissingNode) {
Input(e_.opts().WithName("B"));
Input(a_.opts().WithName("A"));
EXPECT_FALSE(Match());
- EXPECT_EQ("Did not find expected node 'B = Input[]()'", diff_);
+ EXPECT_EQ("Did not find expected node '{{node B}} = Input[]()'", diff_);
}
TEST_F(EqualGraphDefTest, ExtraNode) {
@@ -101,7 +101,7 @@ TEST_F(EqualGraphDefTest, ExtraNode) {
Input(a_.opts().WithName("A"));
Input(a_.opts().WithName("B"));
EXPECT_FALSE(Match());
- EXPECT_EQ("Found unexpected node 'B = Input[]()'", diff_);
+ EXPECT_EQ("Found unexpected node '{{node B}} = Input[]()'", diff_);
}
TEST_F(EqualGraphDefTest, NodeOrder) {
diff --git a/tensorflow/core/util/mkl_util.h b/tensorflow/core/util/mkl_util.h
index bb447e0393..566a42dbd5 100644
--- a/tensorflow/core/util/mkl_util.h
+++ b/tensorflow/core/util/mkl_util.h
@@ -17,7 +17,6 @@ limitations under the License.
#define TENSORFLOW_CORE_UTIL_MKL_UTIL_H_
#ifdef INTEL_MKL
-#include <string>
#include <vector>
#include <unordered_map>
#include <utility>
@@ -1896,16 +1895,17 @@ class MklPrimitiveFactory {
MklPrimitiveFactory() {}
~MklPrimitiveFactory() {}
- MklPrimitive* GetOp(const std::string& key) {
+ MklPrimitive* GetOp(const string& key) {
auto stream_iter = MklPrimitiveFactory<T>::GetHashMap().find(key);
if (stream_iter == MklPrimitiveFactory<T>::GetHashMap().end()) {
return nullptr;
} else {
+ CHECK(stream_iter->second != nullptr) << "nullptr present in map";
return stream_iter->second;
}
}
- void SetOp(const std::string& key, MklPrimitive* op) {
+ void SetOp(const string& key, MklPrimitive* op) {
auto stream_iter = MklPrimitiveFactory<T>::GetHashMap().find(key);
CHECK(stream_iter == MklPrimitiveFactory<T>::GetHashMap().end());
@@ -1914,8 +1914,8 @@ class MklPrimitiveFactory {
}
private:
- static inline std::unordered_map<std::string, MklPrimitive*>& GetHashMap() {
- static thread_local std::unordered_map<std::string, MklPrimitive*> map_;
+ static inline std::unordered_map<string, MklPrimitive*>& GetHashMap() {
+ static thread_local std::unordered_map<string, MklPrimitive*> map_;
return map_;
}
};
@@ -1943,9 +1943,7 @@ class FactoryKeyCreator {
Append(StringPiece(buffer, sizeof(T)));
}
- std::string GetKey() {
- return key_;
- }
+ string GetKey() { return key_; }
private:
string key_;
@@ -2020,8 +2018,8 @@ class MklReorderPrimitiveFactory : public MklPrimitiveFactory<T> {
MklReorderPrimitiveFactory() {};
~MklReorderPrimitiveFactory() {};
- static std::string CreateKey(const memory* from, const memory* to) {
- std::string prefix = "reorder";
+ static string CreateKey(const memory* from, const memory* to) {
+ string prefix = "reorder";
FactoryKeyCreator key_creator;
auto const &from_desc = from->get_primitive_desc().desc().data;
auto const &to_desc = to->get_primitive_desc().desc().data;
@@ -2038,12 +2036,12 @@ class MklReorderPrimitiveFactory : public MklPrimitiveFactory<T> {
}
MklPrimitive* GetReorder(const memory* from, const memory* to) {
- std::string key = CreateKey(from, to);
+ string key = CreateKey(from, to);
return this->GetOp(key);
}
void SetReorder(const memory* from, const memory* to, MklPrimitive* op) {
- std::string key = CreateKey(from, to);
+ string key = CreateKey(from, to);
this->SetOp(key, op);
}
};
diff --git a/tensorflow/docs_src/deploy/distributed.md b/tensorflow/docs_src/deploy/distributed.md
index 8e2c818e39..fc3a60603f 100644
--- a/tensorflow/docs_src/deploy/distributed.md
+++ b/tensorflow/docs_src/deploy/distributed.md
@@ -314,7 +314,7 @@ serve multiple clients.
**Cluster**
-A TensorFlow cluster comprises a one or more "jobs", each divided into lists of
+A TensorFlow cluster comprises one or more "jobs", each divided into lists of
one or more "tasks". A cluster is typically dedicated to a particular high-level
objective, such as training a neural network, using many machines in parallel. A
cluster is defined by
diff --git a/tensorflow/docs_src/guide/using_gpu.md b/tensorflow/docs_src/guide/using_gpu.md
index c429ca4750..c0218fd12e 100644
--- a/tensorflow/docs_src/guide/using_gpu.md
+++ b/tensorflow/docs_src/guide/using_gpu.md
@@ -143,7 +143,7 @@ If the device you have specified does not exist, you will get
```
InvalidArgumentError: Invalid argument: Cannot assign a device to node 'b':
Could not satisfy explicit device specification '/device:GPU:2'
- [[Node: b = Const[dtype=DT_FLOAT, value=Tensor<type: float shape: [3,2]
+ [[{{node b}} = Const[dtype=DT_FLOAT, value=Tensor<type: float shape: [3,2]
values: 1 2 3...>, _device="/device:GPU:2"]()]]
```
diff --git a/tensorflow/docs_src/performance/xla/broadcasting.md b/tensorflow/docs_src/performance/xla/broadcasting.md
index eaa709c2f8..7018ded53f 100644
--- a/tensorflow/docs_src/performance/xla/broadcasting.md
+++ b/tensorflow/docs_src/performance/xla/broadcasting.md
@@ -99,7 +99,7 @@ dimensions 1 and 2 of the cuboid.
This type of broadcast is used in the binary ops in `XlaBuilder`, if the
`broadcast_dimensions` argument is given. For example, see
-[XlaBuilder::Add](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.cc).
+[XlaBuilder::Add](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.cc).
In the XLA source code, this type of broadcasting is sometimes called "InDim"
broadcasting.
diff --git a/tensorflow/docs_src/performance/xla/operation_semantics.md b/tensorflow/docs_src/performance/xla/operation_semantics.md
index fe9afc4ecb..5f7482f90f 100644
--- a/tensorflow/docs_src/performance/xla/operation_semantics.md
+++ b/tensorflow/docs_src/performance/xla/operation_semantics.md
@@ -1,7 +1,7 @@
# Operation Semantics
The following describes the semantics of operations defined in the
-[`XlaBuilder`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h)
+[`XlaBuilder`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h)
interface. Typically, these operations map one-to-one to operations defined in
the RPC interface in
[`xla_data.proto`](https://www.tensorflow.org/code/tensorflow/compiler/xla/xla_data.proto).
@@ -16,7 +16,7 @@ and familiar names; for example a *vector* is a 1-dimensional array and a
## BatchNormGrad
See also
-[`XlaBuilder::BatchNormGrad`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h)
+[`XlaBuilder::BatchNormGrad`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h)
and [the original batch normalization paper](https://arxiv.org/abs/1502.03167)
for a detailed description of the algorithm.
@@ -80,7 +80,7 @@ The output type is a tuple of three handles:
## BatchNormInference
See also
-[`XlaBuilder::BatchNormInference`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h)
+[`XlaBuilder::BatchNormInference`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h)
and [the original batch normalization paper](https://arxiv.org/abs/1502.03167)
for a detailed description of the algorithm.
@@ -115,7 +115,7 @@ The output is an n-dimensional, normalized array with the same shape as input
## BatchNormTraining
See also
-[`XlaBuilder::BatchNormTraining`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h)
+[`XlaBuilder::BatchNormTraining`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h)
and [`the original batch normalization paper`](https://arxiv.org/abs/1502.03167)
for a detailed description of the algorithm.
@@ -167,7 +167,7 @@ spatial dimensions using the formulas above.
## BitcastConvertType
See also
-[`XlaBuilder::BitcastConvertType`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::BitcastConvertType`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
Similar to a `tf.bitcast` in TensorFlow, performs an element-wise bitcast
operation from a data shape to a target shape. The dimensions must match, and
@@ -189,7 +189,7 @@ and destination element types must not be tuples.
## Broadcast
See also
-[`XlaBuilder::Broadcast`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Broadcast`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
Adds dimensions to an array by duplicating the data in the array.
@@ -217,7 +217,7 @@ For example, if `operand` is a scalar `f32` with value `2.0f`, and
## Call
See also
-[`XlaBuilder::Call`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Call`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
Invokes a computation with the given arguments.
@@ -236,7 +236,7 @@ The arity and types of the `args` must match the parameters of the
## Clamp
See also
-[`XlaBuilder::Clamp`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Clamp`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
Clamps an operand to within the range between a minimum and maximum value.
@@ -269,7 +269,7 @@ Clamp(min, operand, max) = s32[3]{0, 5, 6};
## Collapse
See also
-[`XlaBuilder::Collapse`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h)
+[`XlaBuilder::Collapse`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h)
and the @{tf.reshape} operation.
Collapses dimensions of an array into one dimension.
@@ -332,7 +332,7 @@ then v12 == f32[8x3] {{10, 11, 12},
## Concatenate
See also
-[`XlaBuilder::ConcatInDim`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::ConcatInDim`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
Concatenate composes an array from multiple array operands. The array is of the
same rank as each of the input array operands (which must be of the same rank as
@@ -388,7 +388,7 @@ Diagram:
## Conditional
See also
-[`XlaBuilder::Conditional`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Conditional`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
<b> `Conditional(pred, true_operand, true_computation, false_operand,
false_computation)` </b>
@@ -416,7 +416,7 @@ executed depending on the value of `pred`.
## Conv (convolution)
See also
-[`XlaBuilder::Conv`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Conv`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
As ConvWithGeneralPadding, but the padding is specified in a short-hand way as
either SAME or VALID. SAME padding pads the input (`lhs`) with zeroes so that
@@ -426,7 +426,7 @@ account. VALID padding simply means no padding.
## ConvWithGeneralPadding (convolution)
See also
-[`XlaBuilder::ConvWithGeneralPadding`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::ConvWithGeneralPadding`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
Computes a convolution of the kind used in neural networks. Here, a convolution
can be thought of as a n-dimensional window moving across a n-dimensional base
@@ -538,7 +538,7 @@ for (b, oz, oy, ox) { // output coordinates
## ConvertElementType
See also
-[`XlaBuilder::ConvertElementType`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::ConvertElementType`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
Similar to an element-wise `static_cast` in C++, performs an element-wise
conversion operation from a data shape to a target shape. The dimensions must
@@ -572,7 +572,7 @@ then b == f32[3]{0.0, 1.0, 2.0}
## CrossReplicaSum
See also
-[`XlaBuilder::CrossReplicaSum`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::CrossReplicaSum`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
Computes a sum across replicas.
@@ -607,7 +607,7 @@ than another.
## CustomCall
See also
-[`XlaBuilder::CustomCall`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::CustomCall`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
Call a user-provided function within a computation.
@@ -668,7 +668,7 @@ idempotent.
## Dot
See also
-[`XlaBuilder::Dot`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Dot`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
<b> `Dot(lhs, rhs)` </b>
@@ -697,7 +697,7 @@ multiplications or matrix/matrix multiplications.
## DotGeneral
See also
-[`XlaBuilder::DotGeneral`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::DotGeneral`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
<b> `DotGeneral(lhs, rhs, dimension_numbers)` </b>
@@ -784,7 +784,7 @@ non-contracting/non-batch dimension.
## DynamicSlice
See also
-[`XlaBuilder::DynamicSlice`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::DynamicSlice`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
DynamicSlice extracts a sub-array from the input array at dynamic
`start_indices`. The size of the slice in each dimension is passed in
@@ -848,7 +848,7 @@ DynamicSlice(b, s, {2, 2}) produces:
## DynamicUpdateSlice
See also
-[`XlaBuilder::DynamicUpdateSlice`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::DynamicUpdateSlice`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
DynamicUpdateSlice generates a result which is the value of the input array
`operand`, with a slice `update` overwritten at `start_indices`.
@@ -920,7 +920,7 @@ DynamicUpdateSlice(b, u, s) produces:
## Element-wise binary arithmetic operations
See also
-[`XlaBuilder::Add`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Add`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
A set of element-wise binary arithmetic operations is supported.
@@ -965,7 +965,7 @@ shapes of both operands. The semantics are described in detail on the
## Element-wise comparison operations
See also
-[`XlaBuilder::Eq`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Eq`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
A set of standard element-wise binary comparison operations is supported. Note
that standard IEEE 754 floating-point comparison semantics apply when comparing
@@ -1051,7 +1051,7 @@ potentially different runtime offset) of an input tensor into an output tensor.
### General Semantics
See also
-[`XlaBuilder::Gather`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Gather`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
For a more intuitive description, see the "Informal Description" section below.
<b> `gather(operand, gather_indices, output_window_dims, elided_window_dims, window_bounds, gather_dims_to_operand_dims)` </b>
@@ -1254,7 +1254,7 @@ concatenation of all these rows.
## GetTupleElement
See also
-[`XlaBuilder::GetTupleElement`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::GetTupleElement`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
Indexes into a tuple with a compile-time-constant value.
@@ -1275,7 +1275,7 @@ See also @{tf.tuple}.
## Infeed
See also
-[`XlaBuilder::Infeed`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Infeed`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
<b> `Infeed(shape)` </b>
@@ -1327,7 +1327,7 @@ Arguments | Type | Semantics
## Map
See also
-[`XlaBuilder::Map`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Map`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
<b> `Map(operands..., computation)` </b>
@@ -1356,7 +1356,7 @@ input arrays to produce the output array.
## Pad
See also
-[`XlaBuilder::Pad`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Pad`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
<b> `Pad(operand, padding_value, padding_config)` </b>
@@ -1395,7 +1395,7 @@ are all 0. The figure below shows examples of different `edge_padding` and
## Recv
See also
-[`XlaBuilder::Recv`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Recv`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
<b> `Recv(shape, channel_handle)` </b>
@@ -1429,7 +1429,7 @@ complete and returns the received data.
## Reduce
See also
-[`XlaBuilder::Reduce`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Reduce`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
Applies a reduction function to an array.
@@ -1546,7 +1546,7 @@ Reducing the 3D array over all its dimensions produces the scalar `84`.
## ReducePrecision
See also
-[`XlaBuilder::ReducePrecision`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::ReducePrecision`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
Models the effect of converting floating-point values to a lower-precision
format (such as IEEE-FP16) and back to the original format. The number of
@@ -1577,7 +1577,7 @@ portion of the conversion is then simply a no-op.
## ReduceWindow
See also
-[`XlaBuilder::ReduceWindow`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::ReduceWindow`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
Applies a reduction function to all elements in each window of the input
multi-dimensional array, producing an output multi-dimensional array with the
@@ -1660,7 +1660,7 @@ context of [`Reduce`](#reduce) for more details.
## Reshape
See also
-[`XlaBuilder::Reshape`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h)
+[`XlaBuilder::Reshape`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h)
and the [`Collapse`](#collapse) operation.
Reshapes the dimensions of an array into a new configuration.
@@ -1741,7 +1741,7 @@ Reshape(5, {}, {1,1}) == f32[1x1] {{5}};
## Rev (reverse)
See also
-[`XlaBuilder::Rev`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Rev`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
<b>`Rev(operand, dimensions)`</b>
@@ -1763,7 +1763,7 @@ the two window dimensions during the gradient computation in neural networks.
## RngNormal
See also
-[`XlaBuilder::RngNormal`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::RngNormal`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
Constructs an output of a given shape with random numbers generated following
the $$N(\mu, \sigma)$$ normal distribution. The parameters `mu` and `sigma`, and
@@ -1783,7 +1783,7 @@ be scalar valued.
## RngUniform
See also
-[`XlaBuilder::RngUniform`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::RngUniform`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
Constructs an output of a given shape with random numbers generated following
the uniform distribution over the interval $$[a,b)$$. The parameters and output
@@ -1804,7 +1804,7 @@ is implementation-defined.
## Select
See also
-[`XlaBuilder::Select`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Select`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
Constructs an output array from elements of two input arrays, based on the
values of a predicate array.
@@ -1855,7 +1855,7 @@ the same shape!) then `pred` has to be a scalar of type `PRED`.
## SelectAndScatter
See also
-[`XlaBuilder::SelectAndScatter`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::SelectAndScatter`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
This operation can be considered as a composite operation that first computes
`ReduceWindow` on the `operand` array to select an element from each window, and
@@ -1935,7 +1935,7 @@ context of [`Reduce`](#reduce) for more details.
## Send
See also
-[`XlaBuilder::Send`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Send`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
<b> `Send(operand, channel_handle)` </b>
@@ -1990,7 +1990,7 @@ computations. For example, below schedules lead to deadlocks.
## Slice
See also
-[`XlaBuilder::Slice`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Slice`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
Slicing extracts a sub-array from the input array. The sub-array is of the same
rank as the input and contains the values inside a bounding box within the input
@@ -2039,7 +2039,7 @@ Slice(b, {2, 1}, {4, 3}) produces:
## Sort
See also
-[`XlaBuilder::Sort`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Sort`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
There are two versions of the Sort instruction: a single-operand and a
two-operand version.
@@ -2099,7 +2099,7 @@ This is the same as Reshape(operand, permutation,
## Tuple
See also
-[`XlaBuilder::Tuple`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::Tuple`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
A tuple containing a variable number of data handles, each of which has its own
shape.
@@ -2118,7 +2118,7 @@ Tuples can be deconstructed (accessed) via the [`GetTupleElement`]
## While
See also
-[`XlaBuilder::While`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_client/xla_builder.h).
+[`XlaBuilder::While`](https://www.tensorflow.org/code/tensorflow/compiler/xla/client/xla_builder.h).
<b> `While(condition, body, init)` </b>
diff --git a/tensorflow/examples/saved_model/saved_model_half_plus_two.py b/tensorflow/examples/saved_model/saved_model_half_plus_two.py
index 0d6f1ef655..2d1e0c6f6d 100644
--- a/tensorflow/examples/saved_model/saved_model_half_plus_two.py
+++ b/tensorflow/examples/saved_model/saved_model_half_plus_two.py
@@ -33,6 +33,13 @@ where `a`, `b` and `c` are variables with `a=0.5` and `b=2` and `c=3`.
Output from this program is typically used to exercise SavedModel load and
execution code.
+
+To create a CPU model:
+ bazel run -c opt saved_half_plus_two -- --device=cpu
+
+To create GPU model:
+ bazel run --config=cuda -c opt saved_half_plus_two -- \
+ --device=gpu
"""
from __future__ import absolute_import
@@ -105,42 +112,52 @@ def _build_classification_signature(input_tensor, scores_tensor):
def _generate_saved_model_for_half_plus_two(export_dir,
as_text=False,
- use_main_op=False):
+ use_main_op=False,
+ device_type="cpu"):
"""Generates SavedModel for half plus two.
Args:
export_dir: The directory to which the SavedModel should be written.
as_text: Writes the SavedModel protocol buffer in text format to disk.
use_main_op: Whether to supply a main op during SavedModel build time.
+ device_name: Device to force ops to run on.
"""
builder = tf.saved_model.builder.SavedModelBuilder(export_dir)
- with tf.Session(graph=tf.Graph()) as sess:
- # Set up the model parameters as variables to exercise variable loading
- # functionality upon restore.
- a = tf.Variable(0.5, name="a")
- b = tf.Variable(2.0, name="b")
- c = tf.Variable(3.0, name="c")
-
- # Create a placeholder for serialized tensorflow.Example messages to be fed.
- serialized_tf_example = tf.placeholder(tf.string, name="tf_example")
-
- # Parse the tensorflow.Example looking for a feature named "x" with a single
- # floating point value.
- feature_configs = {
- "x": tf.FixedLenFeature(
- [1], dtype=tf.float32),
- "x2": tf.FixedLenFeature(
- [1], dtype=tf.float32, default_value=[0.0])
- }
- tf_example = tf.parse_example(serialized_tf_example, feature_configs)
- # Use tf.identity() to assign name
- x = tf.identity(tf_example["x"], name="x")
- y = tf.add(tf.multiply(a, x), b, name="y")
- y2 = tf.add(tf.multiply(a, x), c, name="y2")
-
- x2 = tf.identity(tf_example["x2"], name="x2")
- y3 = tf.add(tf.multiply(a, x2), c, name="y3")
+ device_name = "/cpu:0"
+ if device_type == "gpu":
+ device_name = "/gpu:0"
+
+ with tf.Session(
+ graph=tf.Graph(),
+ config=tf.ConfigProto(log_device_placement=True)) as sess:
+ with tf.device(device_name):
+ # Set up the model parameters as variables to exercise variable loading
+ # functionality upon restore.
+ a = tf.Variable(0.5, name="a")
+ b = tf.Variable(2.0, name="b")
+ c = tf.Variable(3.0, name="c")
+
+ # Create a placeholder for serialized tensorflow.Example messages to be
+ # fed.
+ serialized_tf_example = tf.placeholder(tf.string, name="tf_example")
+
+ # Parse the tensorflow.Example looking for a feature named "x" with a
+ # single floating point value.
+ feature_configs = {
+ "x": tf.FixedLenFeature([1], dtype=tf.float32),
+ "x2": tf.FixedLenFeature([1], dtype=tf.float32, default_value=[0.0])
+ }
+ # parse_example only works on CPU
+ with tf.device("/cpu:0"):
+ tf_example = tf.parse_example(serialized_tf_example, feature_configs)
+ # Use tf.identity() to assign name
+ x = tf.identity(tf_example["x"], name="x")
+ y = tf.add(tf.multiply(a, x), b, name="y")
+ y2 = tf.add(tf.multiply(a, x), c, name="y2")
+
+ x2 = tf.identity(tf_example["x2"], name="x2")
+ y3 = tf.add(tf.multiply(a, x2), c, name="y3")
# Create an assets file that can be saved and restored as part of the
# SavedModel.
@@ -185,20 +202,7 @@ def _generate_saved_model_for_half_plus_two(export_dir,
}
# Initialize all variables and then save the SavedModel.
sess.run(tf.global_variables_initializer())
- signature_def_map = {
- "regress_x_to_y":
- _build_regression_signature(serialized_tf_example, y),
- "regress_x_to_y2":
- _build_regression_signature(serialized_tf_example, y2),
- "regress_x2_to_y3":
- _build_regression_signature(x2, y3),
- "classify_x_to_y":
- _build_classification_signature(serialized_tf_example, y),
- "classify_x2_to_y3":
- _build_classification_signature(x2, y3),
- tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY:
- predict_signature_def
- }
+
if use_main_op:
builder.add_meta_graph_and_variables(
sess, [tf.saved_model.tag_constants.SERVING],
@@ -212,19 +216,30 @@ def _generate_saved_model_for_half_plus_two(export_dir,
signature_def_map=signature_def_map,
assets_collection=tf.get_collection(tf.GraphKeys.ASSET_FILEPATHS),
legacy_init_op=tf.group(assign_filename_op))
- builder.save(as_text)
+ builder.save(as_text)
def main(_):
- _generate_saved_model_for_half_plus_two(FLAGS.output_dir)
- print("SavedModel generated at: %s" % FLAGS.output_dir)
+ _generate_saved_model_for_half_plus_two(
+ FLAGS.output_dir, device_type=FLAGS.device)
+ print("SavedModel generated for %(device)s at: %(dir)s" % {
+ "device": FLAGS.device,
+ "dir": FLAGS.output_dir
+ })
- _generate_saved_model_for_half_plus_two(FLAGS.output_dir_pbtxt, as_text=True)
- print("SavedModel generated at: %s" % FLAGS.output_dir_pbtxt)
+ _generate_saved_model_for_half_plus_two(
+ FLAGS.output_dir_pbtxt, as_text=True, device_type=FLAGS.device)
+ print("SavedModel generated for %(device)s at: %(dir)s" % {
+ "device": FLAGS.device,
+ "dir": FLAGS.output_dir_pbtxt
+ })
_generate_saved_model_for_half_plus_two(
- FLAGS.output_dir_main_op, use_main_op=True)
- print("SavedModel generated at: %s" % FLAGS.output_dir_main_op)
+ FLAGS.output_dir_main_op, use_main_op=True, device_type=FLAGS.device)
+ print("SavedModel generated for %(device)s at: %(dir)s " % {
+ "device": FLAGS.device,
+ "dir": FLAGS.output_dir_main_op
+ })
if __name__ == "__main__":
@@ -244,5 +259,10 @@ if __name__ == "__main__":
type=str,
default="/tmp/saved_model_half_plus_two_main_op",
help="Directory where to output the SavedModel with a main op.")
+ parser.add_argument(
+ "--device",
+ type=str,
+ default="cpu",
+ help="Force model to run on 'cpu' or 'gpu'")
FLAGS, unparsed = parser.parse_known_args()
tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)
diff --git a/tensorflow/go/op/wrappers.go b/tensorflow/go/op/wrappers.go
index 18d7425323..1e765d1cd7 100644
--- a/tensorflow/go/op/wrappers.go
+++ b/tensorflow/go/op/wrappers.go
@@ -4877,6 +4877,146 @@ func Rsqrt(scope *Scope, x tf.Output) (y tf.Output) {
return op.Output(0)
}
+// AudioSpectrogramAttr is an optional argument to AudioSpectrogram.
+type AudioSpectrogramAttr func(optionalAttr)
+
+// AudioSpectrogramMagnitudeSquared sets the optional magnitude_squared attribute to value.
+//
+// value: Whether to return the squared magnitude or just the
+// magnitude. Using squared magnitude can avoid extra calculations.
+// If not specified, defaults to false
+func AudioSpectrogramMagnitudeSquared(value bool) AudioSpectrogramAttr {
+ return func(m optionalAttr) {
+ m["magnitude_squared"] = value
+ }
+}
+
+// Produces a visualization of audio data over time.
+//
+// Spectrograms are a standard way of representing audio information as a series of
+// slices of frequency information, one slice for each window of time. By joining
+// these together into a sequence, they form a distinctive fingerprint of the sound
+// over time.
+//
+// This op expects to receive audio data as an input, stored as floats in the range
+// -1 to 1, together with a window width in samples, and a stride specifying how
+// far to move the window between slices. From this it generates a three
+// dimensional output. The lowest dimension has an amplitude value for each
+// frequency during that time slice. The next dimension is time, with successive
+// frequency slices. The final dimension is for the channels in the input, so a
+// stereo audio input would have two here for example.
+//
+// This means the layout when converted and saved as an image is rotated 90 degrees
+// clockwise from a typical spectrogram. Time is descending down the Y axis, and
+// the frequency decreases from left to right.
+//
+// Each value in the result represents the square root of the sum of the real and
+// imaginary parts of an FFT on the current window of samples. In this way, the
+// lowest dimension represents the power of each frequency in the current window,
+// and adjacent windows are concatenated in the next dimension.
+//
+// To get a more intuitive and visual look at what this operation does, you can run
+// tensorflow/examples/wav_to_spectrogram to read in an audio file and save out the
+// resulting spectrogram as a PNG image.
+//
+// Arguments:
+// input: Float representation of audio data.
+// window_size: How wide the input window is in samples. For the highest efficiency
+// this should be a power of two, but other values are accepted.
+// stride: How widely apart the center of adjacent sample windows should be.
+//
+// Returns 3D representation of the audio frequencies as an image.
+func AudioSpectrogram(scope *Scope, input tf.Output, window_size int64, stride int64, optional ...AudioSpectrogramAttr) (spectrogram tf.Output) {
+ if scope.Err() != nil {
+ return
+ }
+ attrs := map[string]interface{}{"window_size": window_size, "stride": stride}
+ for _, a := range optional {
+ a(attrs)
+ }
+ opspec := tf.OpSpec{
+ Type: "AudioSpectrogram",
+ Input: []tf.Input{
+ input,
+ },
+ Attrs: attrs,
+ }
+ op := scope.AddOperation(opspec)
+ return op.Output(0)
+}
+
+// CTCBeamSearchDecoderAttr is an optional argument to CTCBeamSearchDecoder.
+type CTCBeamSearchDecoderAttr func(optionalAttr)
+
+// CTCBeamSearchDecoderMergeRepeated sets the optional merge_repeated attribute to value.
+//
+// value: If true, merge repeated classes in output.
+// If not specified, defaults to true
+func CTCBeamSearchDecoderMergeRepeated(value bool) CTCBeamSearchDecoderAttr {
+ return func(m optionalAttr) {
+ m["merge_repeated"] = value
+ }
+}
+
+// Performs beam search decoding on the logits given in input.
+//
+// A note about the attribute merge_repeated: For the beam search decoder,
+// this means that if consecutive entries in a beam are the same, only
+// the first of these is emitted. That is, when the top path is "A B B B B",
+// "A B" is returned if merge_repeated = True but "A B B B B" is
+// returned if merge_repeated = False.
+//
+// Arguments:
+// inputs: 3-D, shape: `(max_time x batch_size x num_classes)`, the logits.
+// sequence_length: A vector containing sequence lengths, size `(batch)`.
+// beam_width: A scalar >= 0 (beam search beam width).
+// top_paths: A scalar >= 0, <= beam_width (controls output size).
+//
+// Returns A list (length: top_paths) of indices matrices. Matrix j,
+// size `(total_decoded_outputs[j] x 2)`, has indices of a
+// `SparseTensor<int64, 2>`. The rows store: [batch, time].A list (length: top_paths) of values vectors. Vector j,
+// size `(length total_decoded_outputs[j])`, has the values of a
+// `SparseTensor<int64, 2>`. The vector stores the decoded classes for beam j.A list (length: top_paths) of shape vector. Vector j,
+// size `(2)`, stores the shape of the decoded `SparseTensor[j]`.
+// Its values are: `[batch_size, max_decoded_length[j]]`.A matrix, shaped: `(batch_size x top_paths)`. The
+// sequence log-probabilities.
+func CTCBeamSearchDecoder(scope *Scope, inputs tf.Output, sequence_length tf.Output, beam_width int64, top_paths int64, optional ...CTCBeamSearchDecoderAttr) (decoded_indices []tf.Output, decoded_values []tf.Output, decoded_shape []tf.Output, log_probability tf.Output) {
+ if scope.Err() != nil {
+ return
+ }
+ attrs := map[string]interface{}{"beam_width": beam_width, "top_paths": top_paths}
+ for _, a := range optional {
+ a(attrs)
+ }
+ opspec := tf.OpSpec{
+ Type: "CTCBeamSearchDecoder",
+ Input: []tf.Input{
+ inputs, sequence_length,
+ },
+ Attrs: attrs,
+ }
+ op := scope.AddOperation(opspec)
+ if scope.Err() != nil {
+ return
+ }
+ var idx int
+ var err error
+ if decoded_indices, idx, err = makeOutputList(op, idx, "decoded_indices"); err != nil {
+ scope.UpdateErr("CTCBeamSearchDecoder", err)
+ return
+ }
+ if decoded_values, idx, err = makeOutputList(op, idx, "decoded_values"); err != nil {
+ scope.UpdateErr("CTCBeamSearchDecoder", err)
+ return
+ }
+ if decoded_shape, idx, err = makeOutputList(op, idx, "decoded_shape"); err != nil {
+ scope.UpdateErr("CTCBeamSearchDecoder", err)
+ return
+ }
+ log_probability = op.Output(idx)
+ return decoded_indices, decoded_values, decoded_shape, log_probability
+}
+
// MatrixInverseAttr is an optional argument to MatrixInverse.
type MatrixInverseAttr func(optionalAttr)
@@ -5225,12 +5365,26 @@ func IsBoostedTreesEnsembleInitialized(scope *Scope, tree_ensemble_handle tf.Out
return op.Output(0)
}
+// CastAttr is an optional argument to Cast.
+type CastAttr func(optionalAttr)
+
+// CastTruncate sets the optional Truncate attribute to value.
+// If not specified, defaults to false
+func CastTruncate(value bool) CastAttr {
+ return func(m optionalAttr) {
+ m["Truncate"] = value
+ }
+}
+
// Cast x of type SrcT to y of DstT.
-func Cast(scope *Scope, x tf.Output, DstT tf.DataType) (y tf.Output) {
+func Cast(scope *Scope, x tf.Output, DstT tf.DataType, optional ...CastAttr) (y tf.Output) {
if scope.Err() != nil {
return
}
attrs := map[string]interface{}{"DstT": DstT}
+ for _, a := range optional {
+ a(attrs)
+ }
opspec := tf.OpSpec{
Type: "Cast",
Input: []tf.Input{
@@ -6015,146 +6169,6 @@ func Dilation2DBackpropInput(scope *Scope, input tf.Output, filter tf.Output, ou
return op.Output(0)
}
-// CTCBeamSearchDecoderAttr is an optional argument to CTCBeamSearchDecoder.
-type CTCBeamSearchDecoderAttr func(optionalAttr)
-
-// CTCBeamSearchDecoderMergeRepeated sets the optional merge_repeated attribute to value.
-//
-// value: If true, merge repeated classes in output.
-// If not specified, defaults to true
-func CTCBeamSearchDecoderMergeRepeated(value bool) CTCBeamSearchDecoderAttr {
- return func(m optionalAttr) {
- m["merge_repeated"] = value
- }
-}
-
-// Performs beam search decoding on the logits given in input.
-//
-// A note about the attribute merge_repeated: For the beam search decoder,
-// this means that if consecutive entries in a beam are the same, only
-// the first of these is emitted. That is, when the top path is "A B B B B",
-// "A B" is returned if merge_repeated = True but "A B B B B" is
-// returned if merge_repeated = False.
-//
-// Arguments:
-// inputs: 3-D, shape: `(max_time x batch_size x num_classes)`, the logits.
-// sequence_length: A vector containing sequence lengths, size `(batch)`.
-// beam_width: A scalar >= 0 (beam search beam width).
-// top_paths: A scalar >= 0, <= beam_width (controls output size).
-//
-// Returns A list (length: top_paths) of indices matrices. Matrix j,
-// size `(total_decoded_outputs[j] x 2)`, has indices of a
-// `SparseTensor<int64, 2>`. The rows store: [batch, time].A list (length: top_paths) of values vectors. Vector j,
-// size `(length total_decoded_outputs[j])`, has the values of a
-// `SparseTensor<int64, 2>`. The vector stores the decoded classes for beam j.A list (length: top_paths) of shape vector. Vector j,
-// size `(2)`, stores the shape of the decoded `SparseTensor[j]`.
-// Its values are: `[batch_size, max_decoded_length[j]]`.A matrix, shaped: `(batch_size x top_paths)`. The
-// sequence log-probabilities.
-func CTCBeamSearchDecoder(scope *Scope, inputs tf.Output, sequence_length tf.Output, beam_width int64, top_paths int64, optional ...CTCBeamSearchDecoderAttr) (decoded_indices []tf.Output, decoded_values []tf.Output, decoded_shape []tf.Output, log_probability tf.Output) {
- if scope.Err() != nil {
- return
- }
- attrs := map[string]interface{}{"beam_width": beam_width, "top_paths": top_paths}
- for _, a := range optional {
- a(attrs)
- }
- opspec := tf.OpSpec{
- Type: "CTCBeamSearchDecoder",
- Input: []tf.Input{
- inputs, sequence_length,
- },
- Attrs: attrs,
- }
- op := scope.AddOperation(opspec)
- if scope.Err() != nil {
- return
- }
- var idx int
- var err error
- if decoded_indices, idx, err = makeOutputList(op, idx, "decoded_indices"); err != nil {
- scope.UpdateErr("CTCBeamSearchDecoder", err)
- return
- }
- if decoded_values, idx, err = makeOutputList(op, idx, "decoded_values"); err != nil {
- scope.UpdateErr("CTCBeamSearchDecoder", err)
- return
- }
- if decoded_shape, idx, err = makeOutputList(op, idx, "decoded_shape"); err != nil {
- scope.UpdateErr("CTCBeamSearchDecoder", err)
- return
- }
- log_probability = op.Output(idx)
- return decoded_indices, decoded_values, decoded_shape, log_probability
-}
-
-// AudioSpectrogramAttr is an optional argument to AudioSpectrogram.
-type AudioSpectrogramAttr func(optionalAttr)
-
-// AudioSpectrogramMagnitudeSquared sets the optional magnitude_squared attribute to value.
-//
-// value: Whether to return the squared magnitude or just the
-// magnitude. Using squared magnitude can avoid extra calculations.
-// If not specified, defaults to false
-func AudioSpectrogramMagnitudeSquared(value bool) AudioSpectrogramAttr {
- return func(m optionalAttr) {
- m["magnitude_squared"] = value
- }
-}
-
-// Produces a visualization of audio data over time.
-//
-// Spectrograms are a standard way of representing audio information as a series of
-// slices of frequency information, one slice for each window of time. By joining
-// these together into a sequence, they form a distinctive fingerprint of the sound
-// over time.
-//
-// This op expects to receive audio data as an input, stored as floats in the range
-// -1 to 1, together with a window width in samples, and a stride specifying how
-// far to move the window between slices. From this it generates a three
-// dimensional output. The lowest dimension has an amplitude value for each
-// frequency during that time slice. The next dimension is time, with successive
-// frequency slices. The final dimension is for the channels in the input, so a
-// stereo audio input would have two here for example.
-//
-// This means the layout when converted and saved as an image is rotated 90 degrees
-// clockwise from a typical spectrogram. Time is descending down the Y axis, and
-// the frequency decreases from left to right.
-//
-// Each value in the result represents the square root of the sum of the real and
-// imaginary parts of an FFT on the current window of samples. In this way, the
-// lowest dimension represents the power of each frequency in the current window,
-// and adjacent windows are concatenated in the next dimension.
-//
-// To get a more intuitive and visual look at what this operation does, you can run
-// tensorflow/examples/wav_to_spectrogram to read in an audio file and save out the
-// resulting spectrogram as a PNG image.
-//
-// Arguments:
-// input: Float representation of audio data.
-// window_size: How wide the input window is in samples. For the highest efficiency
-// this should be a power of two, but other values are accepted.
-// stride: How widely apart the center of adjacent sample windows should be.
-//
-// Returns 3D representation of the audio frequencies as an image.
-func AudioSpectrogram(scope *Scope, input tf.Output, window_size int64, stride int64, optional ...AudioSpectrogramAttr) (spectrogram tf.Output) {
- if scope.Err() != nil {
- return
- }
- attrs := map[string]interface{}{"window_size": window_size, "stride": stride}
- for _, a := range optional {
- a(attrs)
- }
- opspec := tf.OpSpec{
- Type: "AudioSpectrogram",
- Input: []tf.Input{
- input,
- },
- Attrs: attrs,
- }
- op := scope.AddOperation(opspec)
- return op.Output(0)
-}
-
// Compute the polygamma function \\(\psi^{(n)}(x)\\).
//
// The polygamma function is defined as:
diff --git a/tensorflow/java/BUILD b/tensorflow/java/BUILD
index 73e210fae0..7ceba3903d 100644
--- a/tensorflow/java/BUILD
+++ b/tensorflow/java/BUILD
@@ -292,6 +292,19 @@ tf_java_test(
],
)
+tf_java_test(
+ name = "GradientsTest",
+ size = "small",
+ srcs = ["src/test/java/org/tensorflow/op/core/GradientsTest.java"],
+ javacopts = JAVACOPTS,
+ test_class = "org.tensorflow.op.core.GradientsTest",
+ deps = [
+ ":tensorflow",
+ ":testutil",
+ "@junit",
+ ],
+)
+
filegroup(
name = "processor_test_resources",
srcs = glob([
diff --git a/tensorflow/java/maven/README.md b/tensorflow/java/maven/README.md
index 3e030dcd09..cbc64a284f 100644
--- a/tensorflow/java/maven/README.md
+++ b/tensorflow/java/maven/README.md
@@ -151,16 +151,6 @@ conducted in a [Docker](https://www.docker.com) container.
7. Upon successful release, commit changes to all the `pom.xml` files
(which should have the updated version number).
-### Snapshots
-
-If the `TF_VERSION` provided to the `release.sh` script ends in `-SNAPSHOT`,
-then instead of using official release files, the nightly build artifacts from
-https://ci.tensorflow.org/view/Nightly/job/nightly-libtensorflow/,
-https://ci.tensorflow.org/view/Nightly/job/nightly-libtensorflow-windows/ and
-https://ci.tensorflow.org/view/Nightly/job/nightly-android
-will be used to upload to the Maven Central snapshots repository. (Note that
-snapshots are only uploaded to Maven Central, not Bintray.)
-
### Skip deploying to a repository
Should you need, setting environment variables `DEPLOY_OSSRH=0` or
@@ -173,12 +163,12 @@ cannot skip deploying to OSSRH for a `-SNAPSHOT` version.
This section provides some pointers around how artifacts are currently
assembled.
-All native and java code is first built and tested on
-a [Tensorflow Jenkins server](https://ci.tensorflow.org/) which run various
-scripts under the [`tools/ci_build`](../../tools/ci_build/) directory. Of
-particular interest may be `tools/ci_build/builds/libtensorflow.sh` which
-bundles Java-related build sources and outputs into archives, and
-`tools/ci_build/builds/android_full.sh` which produces an Android AAR package.
+All native and java code is first built and tested by the release process
+which run various scripts under the [`tools/ci_build`](../../tools/ci_build/)
+directory. Of particular interest may be
+`tools/ci_build/builds/libtensorflow.sh` which bundles Java-related build
+sources and outputs into archives, and `tools/ci_build/builds/android_full.sh`
+which produces an Android AAR package.
Maven artifacts however are not created in Jenkins. Instead, artifacts are
created and deployed externally on-demand, when a maintainer runs the
diff --git a/tensorflow/java/maven/hadoop/pom.xml b/tensorflow/java/maven/hadoop/pom.xml
index 2c2c4106cb..7fa751a46a 100644
--- a/tensorflow/java/maven/hadoop/pom.xml
+++ b/tensorflow/java/maven/hadoop/pom.xml
@@ -5,7 +5,7 @@
<groupId>org.tensorflow</groupId>
<artifactId>hadoop</artifactId>
<packaging>jar</packaging>
- <version>1.10.0-rc0</version>
+ <version>1.10.0-rc1</version>
<name>tensorflow-hadoop</name>
<url>https://www.tensorflow.org</url>
<description>TensorFlow TFRecord InputFormat/OutputFormat for Apache Hadoop</description>
diff --git a/tensorflow/java/maven/libtensorflow/pom.xml b/tensorflow/java/maven/libtensorflow/pom.xml
index 5d4e04ecd3..8ecabfd399 100644
--- a/tensorflow/java/maven/libtensorflow/pom.xml
+++ b/tensorflow/java/maven/libtensorflow/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>org.tensorflow</groupId>
<artifactId>parentpom</artifactId>
- <version>1.10.0-rc0</version>
+ <version>1.10.0-rc1</version>
<relativePath>../</relativePath>
</parent>
<artifactId>libtensorflow</artifactId>
diff --git a/tensorflow/java/maven/libtensorflow_jni/pom.xml b/tensorflow/java/maven/libtensorflow_jni/pom.xml
index e107904f7d..e03ce32216 100644
--- a/tensorflow/java/maven/libtensorflow_jni/pom.xml
+++ b/tensorflow/java/maven/libtensorflow_jni/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>org.tensorflow</groupId>
<artifactId>parentpom</artifactId>
- <version>1.10.0-rc0</version>
+ <version>1.10.0-rc1</version>
<relativePath>../</relativePath>
</parent>
<artifactId>libtensorflow_jni</artifactId>
diff --git a/tensorflow/java/maven/libtensorflow_jni_gpu/pom.xml b/tensorflow/java/maven/libtensorflow_jni_gpu/pom.xml
index b3c525233f..fee840f547 100644
--- a/tensorflow/java/maven/libtensorflow_jni_gpu/pom.xml
+++ b/tensorflow/java/maven/libtensorflow_jni_gpu/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>org.tensorflow</groupId>
<artifactId>parentpom</artifactId>
- <version>1.10.0-rc0</version>
+ <version>1.10.0-rc1</version>
<relativePath>../</relativePath>
</parent>
<artifactId>libtensorflow_jni_gpu</artifactId>
diff --git a/tensorflow/java/maven/pom.xml b/tensorflow/java/maven/pom.xml
index a2943a3172..0c33819b2b 100644
--- a/tensorflow/java/maven/pom.xml
+++ b/tensorflow/java/maven/pom.xml
@@ -6,7 +6,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.tensorflow</groupId>
<artifactId>parentpom</artifactId>
- <version>1.10.0-rc0</version>
+ <version>1.10.0-rc1</version>
<packaging>pom</packaging>
<url>https://www.tensorflow.org</url>
diff --git a/tensorflow/java/maven/proto/pom.xml b/tensorflow/java/maven/proto/pom.xml
index 7080d81b7d..2af7a5cd2e 100644
--- a/tensorflow/java/maven/proto/pom.xml
+++ b/tensorflow/java/maven/proto/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>org.tensorflow</groupId>
<artifactId>parentpom</artifactId>
- <version>1.10.0-rc0</version>
+ <version>1.10.0-rc1</version>
<relativePath>../</relativePath>
</parent>
<artifactId>proto</artifactId>
diff --git a/tensorflow/java/maven/run_inside_container.sh b/tensorflow/java/maven/run_inside_container.sh
index 2240d6b7b9..f4794d68a9 100644
--- a/tensorflow/java/maven/run_inside_container.sh
+++ b/tensorflow/java/maven/run_inside_container.sh
@@ -26,12 +26,6 @@ TF_ECOSYSTEM_URL="https://github.com/tensorflow/ecosystem.git"
DEPLOY_BINTRAY="${DEPLOY_BINTRAY:-true}"
DEPLOY_OSSRH="${DEPLOY_OSSRH:-true}"
-IS_SNAPSHOT="false"
-if [[ "${TF_VERSION}" == *"-SNAPSHOT" ]]; then
- IS_SNAPSHOT="true"
- # Bintray does not allow snapshots.
- DEPLOY_BINTRAY="false"
-fi
PROTOC_RELEASE_URL="https://github.com/google/protobuf/releases/download/v3.5.1/protoc-3.5.1-linux-x86_64.zip"
if [[ "${DEPLOY_BINTRAY}" != "true" && "${DEPLOY_OSSRH}" != "true" ]]; then
echo "Must deploy to at least one of Bintray or OSSRH" >&2
@@ -69,11 +63,7 @@ mvn_property() {
}
download_libtensorflow() {
- if [[ "${IS_SNAPSHOT}" == "true" ]]; then
- URL="http://ci.tensorflow.org/view/Nightly/job/nightly-libtensorflow/TYPE=cpu-slave/lastSuccessfulBuild/artifact/lib_package/libtensorflow-src.jar"
- else
- URL="${RELEASE_URL_PREFIX}/libtensorflow-src-${TF_VERSION}.jar"
- fi
+ URL="${RELEASE_URL_PREFIX}/libtensorflow-src-${TF_VERSION}.jar"
curl -L "${URL}" -o /tmp/src.jar
cd "${DIR}/libtensorflow"
jar -xvf /tmp/src.jar
@@ -101,17 +91,9 @@ download_libtensorflow_jni() {
mkdir windows-x86_64
mkdir darwin-x86_64
- if [[ "${IS_SNAPSHOT}" == "true" ]]; then
- # Nightly builds from http://ci.tensorflow.org/view/Nightly/job/nightly-libtensorflow/
- # and http://ci.tensorflow.org/view/Nightly/job/nightly-libtensorflow-windows/
- curl -L "http://ci.tensorflow.org/view/Nightly/job/nightly-libtensorflow/TYPE=cpu-slave/lastSuccessfulBuild/artifact/lib_package/libtensorflow_jni-cpu-linux-x86_64.tar.gz" | tar -xvz -C linux-x86_64
- curl -L "http://ci.tensorflow.org/view/Nightly/job/nightly-libtensorflow/TYPE=mac-slave/lastSuccessfulBuild/artifact/lib_package/libtensorflow_jni-cpu-darwin-x86_64.tar.gz" | tar -xvz -C darwin-x86_64
- curl -L "http://ci.tensorflow.org/view/Nightly/job/nightly-libtensorflow-windows/lastSuccessfulBuild/artifact/lib_package/libtensorflow_jni-cpu-windows-x86_64.zip" -o /tmp/windows.zip
- else
- curl -L "${RELEASE_URL_PREFIX}/libtensorflow_jni-cpu-linux-x86_64-${TF_VERSION}.tar.gz" | tar -xvz -C linux-x86_64
- curl -L "${RELEASE_URL_PREFIX}/libtensorflow_jni-cpu-darwin-x86_64-${TF_VERSION}.tar.gz" | tar -xvz -C darwin-x86_64
- curl -L "${RELEASE_URL_PREFIX}/libtensorflow_jni-cpu-windows-x86_64-${TF_VERSION}.zip" -o /tmp/windows.zip
- fi
+ curl -L "${RELEASE_URL_PREFIX}/libtensorflow_jni-cpu-linux-x86_64-${TF_VERSION}.tar.gz" | tar -xvz -C linux-x86_64
+ curl -L "${RELEASE_URL_PREFIX}/libtensorflow_jni-cpu-darwin-x86_64-${TF_VERSION}.tar.gz" | tar -xvz -C darwin-x86_64
+ curl -L "${RELEASE_URL_PREFIX}/libtensorflow_jni-cpu-windows-x86_64-${TF_VERSION}.zip" -o /tmp/windows.zip
unzip /tmp/windows.zip -d windows-x86_64
rm -f /tmp/windows.zip
@@ -129,13 +111,7 @@ download_libtensorflow_jni_gpu() {
mkdir linux-x86_64
- if [[ "${IS_SNAPSHOT}" == "true" ]]; then
- # Nightly builds from http://ci.tensorflow.org/view/Nightly/job/nightly-libtensorflow/
- # and http://ci.tensorflow.org/view/Nightly/job/nightly-libtensorflow-windows/
- curl -L "http://ci.tensorflow.org/view/Nightly/job/nightly-libtensorflow/TYPE=gpu-linux/lastSuccessfulBuild/artifact/lib_package/libtensorflow_jni-gpu-linux-x86_64.tar.gz" | tar -xvz -C linux-x86_64
- else
- curl -L "${RELEASE_URL_PREFIX}/libtensorflow_jni-gpu-linux-x86_64-${TF_VERSION}.tar.gz" | tar -xvz -C linux-x86_64
- fi
+ curl -L "${RELEASE_URL_PREFIX}/libtensorflow_jni-gpu-linux-x86_64-${TF_VERSION}.tar.gz" | tar -xvz -C linux-x86_64
# Updated timestamps seem to be required to get Maven to pick up the file.
touch linux-x86_64/*
@@ -165,11 +141,7 @@ generate_java_protos() {
rm -f "/tmp/protoc.zip"
# Download the release archive of TensorFlow protos.
- if [[ "${IS_SNAPSHOT}" == "true" ]]; then
- URL="http://ci.tensorflow.org/view/Nightly/job/nightly-libtensorflow/TYPE=cpu-slave/lastSuccessfulBuild/artifact/lib_package/libtensorflow_proto.zip"
- else
- URL="${RELEASE_URL_PREFIX}/libtensorflow_proto-${TF_VERSION}.zip"
- fi
+ URL="${RELEASE_URL_PREFIX}/libtensorflow_proto-${TF_VERSION}.zip"
curl -L "${URL}" -o /tmp/libtensorflow_proto.zip
mkdir -p "${DIR}/proto/tmp/src"
unzip -d "${DIR}/proto/tmp/src" "/tmp/libtensorflow_proto.zip"
@@ -238,11 +210,7 @@ deploy_profile() {
# Determine the correct pom file property to use
# for the repository url.
local rtype
- if [[ "${IS_SNAPSHOT}" == "true" ]]; then
- rtype='snapshotRepository'
- else
- rtype='repository'
- fi
+ rtype='repository'
local url=$(mvn_property "${profile}" "project.distributionManagement.${rtype}.url")
local repositoryId=$(mvn_property "${profile}" "project.distributionManagement.${rtype}.id")
mvn gpg:sign-and-deploy-file \
@@ -300,17 +268,13 @@ mvn verify
deploy_artifacts
set +ex
-if [[ "${IS_SNAPSHOT}" == "false" ]]; then
- echo "Uploaded to the staging repository"
- echo "After validating the release: "
- if [[ "${DEPLOY_OSSRH}" == "true" ]]; then
- echo "* Login to https://oss.sonatype.org/#stagingRepositories"
- echo "* Find the 'org.tensorflow' staging release and click either 'Release' to release or 'Drop' to abort"
- fi
- if [[ "${DEPLOY_BINTRAY}" == "true" ]]; then
- echo "* Login to https://bintray.com/google/tensorflow/tensorflow"
- echo "* Either 'Publish' unpublished items to release, or 'Discard' to abort"
- fi
-else
- echo "Uploaded to the snapshot repository"
+echo "Uploaded to the staging repository"
+echo "After validating the release: "
+if [[ "${DEPLOY_OSSRH}" == "true" ]]; then
+ echo "* Login to https://oss.sonatype.org/#stagingRepositories"
+ echo "* Find the 'org.tensorflow' staging release and click either 'Release' to release or 'Drop' to abort"
+fi
+if [[ "${DEPLOY_BINTRAY}" == "true" ]]; then
+ echo "* Login to https://bintray.com/google/tensorflow/tensorflow"
+ echo "* Either 'Publish' unpublished items to release, or 'Discard' to abort"
fi
diff --git a/tensorflow/java/maven/spark-connector/pom.xml b/tensorflow/java/maven/spark-connector/pom.xml
index 003d09a0b7..27d9b54c6c 100644
--- a/tensorflow/java/maven/spark-connector/pom.xml
+++ b/tensorflow/java/maven/spark-connector/pom.xml
@@ -6,7 +6,7 @@
<groupId>org.tensorflow</groupId>
<artifactId>spark-connector_2.11</artifactId>
<packaging>jar</packaging>
- <version>1.10.0-rc0</version>
+ <version>1.10.0-rc1</version>
<name>spark-tensorflow-connector</name>
<url>https://www.tensorflow.org</url>
<description>TensorFlow TFRecord connector for Apache Spark DataFrames</description>
diff --git a/tensorflow/java/maven/tensorflow-android/update.py b/tensorflow/java/maven/tensorflow-android/update.py
index 2206d800ca..c620564072 100644
--- a/tensorflow/java/maven/tensorflow-android/update.py
+++ b/tensorflow/java/maven/tensorflow-android/update.py
@@ -86,19 +86,10 @@ def read_template(path):
def main():
args = get_args()
- # Artifacts are downloaded from the ci build. A SNAPSHOT release is
- # associated with artifacts from the last successful nightly build. Otherwise,
- # it comes from the officially blessed release artifacts.
- if args.version.endswith('SNAPSHOT'):
- info_url = ('https://ci.tensorflow.org/view/Nightly/job/nightly-android'
- '/lastSuccessfulBuild/api/json')
- aar_url = None
- build_type = 'nightly-android'
- else:
- release_prefix = 'https://storage.googleapis.com/tensorflow/libtensorflow'
- info_url = '%s/android_buildinfo-%s.json' % (release_prefix, args.version)
- aar_url = '%s/tensorflow-%s.aar' % (release_prefix, args.version)
- build_type = 'release-android'
+ release_prefix = 'https://storage.googleapis.com/tensorflow/libtensorflow'
+ info_url = '%s/android_buildinfo-%s.json' % (release_prefix, args.version)
+ aar_url = '%s/tensorflow-%s.aar' % (release_prefix, args.version)
+ build_type = 'release-android'
# Retrieve build information
build_info = get_json(info_url)
diff --git a/tensorflow/java/maven/tensorflow/pom.xml b/tensorflow/java/maven/tensorflow/pom.xml
index b9affbf699..c952545bc6 100644
--- a/tensorflow/java/maven/tensorflow/pom.xml
+++ b/tensorflow/java/maven/tensorflow/pom.xml
@@ -6,7 +6,7 @@
<parent>
<groupId>org.tensorflow</groupId>
<artifactId>parentpom</artifactId>
- <version>1.10.0-rc0</version>
+ <version>1.10.0-rc1</version>
<relativePath>../</relativePath>
</parent>
<artifactId>tensorflow</artifactId>
diff --git a/tensorflow/java/src/gen/java/org/tensorflow/processor/OperatorProcessor.java b/tensorflow/java/src/gen/java/org/tensorflow/processor/OperatorProcessor.java
index 796d6a62dc..1b7bcdab35 100644
--- a/tensorflow/java/src/gen/java/org/tensorflow/processor/OperatorProcessor.java
+++ b/tensorflow/java/src/gen/java/org/tensorflow/processor/OperatorProcessor.java
@@ -290,7 +290,7 @@ public final class OperatorProcessor extends AbstractProcessor {
javadoc.append(tag).append('\n');
}
}
- javadoc.append("@see {@link ").append(opClassName).append("}\n");
+ javadoc.append("@see ").append(opClassName).append("\n");
return javadoc.toString();
}
diff --git a/tensorflow/java/src/main/java/org/tensorflow/Graph.java b/tensorflow/java/src/main/java/org/tensorflow/Graph.java
index 7d19696749..752b49af04 100644
--- a/tensorflow/java/src/main/java/org/tensorflow/Graph.java
+++ b/tensorflow/java/src/main/java/org/tensorflow/Graph.java
@@ -144,21 +144,29 @@ public final class Graph implements AutoCloseable {
}
/**
- * Adds operations to compute the partial derivatives of sum of {@code y}s w.r.t {@code x}s,
- * i.e., {@code d(y_1 + y_2 + ...)/dx_1, d(y_1 + y_2 + ...)/dx_2...}
- * <p>
- * {@code dx} are used as initial gradients (which represent the symbolic partial derivatives of some loss function
- * {@code L} w.r.t. {@code y}). {@code dx} must be null or have size of {@code y}.
- * <p>
- * If {@code dx} is null, the implementation will use dx of {@link org.tensorflow.op.core.OnesLike OnesLike} for all
- * shapes in {@code y}.
- *
+ * Adds operations to compute the partial derivatives of sum of {@code y}s w.r.t {@code x}s, i.e.,
+ * {@code d(y_1 + y_2 + ...)/dx_1, d(y_1 + y_2 + ...)/dx_2...}
+ *
+ * <p>{@code dx} are used as initial gradients (which represent the symbolic partial derivatives
+ * of some loss function {@code L} w.r.t. {@code y}). {@code dx} must be null or have size of
+ * {@code y}.
+ *
+ * <p>If {@code dx} is null, the implementation will use dx of {@link
+ * org.tensorflow.op.core.OnesLike OnesLike} for all shapes in {@code y}.
+ *
+ * <p>{@code prefix} is used as the name prefix applied to all nodes added to the graph to compute
+ * gradients. It must be unique within the provided graph or the operation will fail.
+ *
+ * <p>If {@code prefix} is null, then one will be chosen automatically.
+ *
+ * @param prefix unique string prefix applied before the names of nodes added to the graph to
+ * compute gradients. If null, a default one will be chosen.
* @param y output of the function to derive
* @param x inputs of the function for which partial derivatives are computed
* @param dx if not null, the partial derivatives of some loss function {@code L} w.r.t. {@code y}
* @return the partial derivatives {@code dy} with the size of {@code x}
*/
- public Output<?>[] addGradients(Output<?>[] y, Output<?>[] x, Output<?>[] dx) {
+ public Output<?>[] addGradients(String prefix, Output<?>[] y, Output<?>[] x, Output<?>[] dx) {
Output<?>[] dy = new Output<?>[x.length];
final long[] yHandles = new long[y.length];
final int[] yIndices = new int[y.length];
@@ -185,12 +193,21 @@ public final class Graph implements AutoCloseable {
dxIndices[i] = dx[i].index();
}
}
- // Gradient outputs are returned in two continuous arrays concatenated into one. The first holds the native handles
- // of the gradient operations while the second holds the index of their output
- // e.g. given xHandles = [x0Handle, x1Handle, ...] and xIndices = [x0Index, x1Index, ..], we obtain
+ // Gradient outputs are returned in two continuous arrays concatenated into one. The first
+ // holds the native handles of the gradient operations while the second holds the index of
+ // their output e.g. given
+ // xHandles = [x0Handle, x1Handle, ...] and xIndices = [x0Index, x1Index, ..], we obtain
// dy = [dy0Handle, dy1Handle, ..., dy0Index, dy1Index, ...]
long[] dyHandlesAndIndices =
- addGradients(ref.nativeHandle(), yHandles, yIndices, xHandles, xIndices, dxHandles, dxIndices);
+ addGradients(
+ ref.nativeHandle(),
+ prefix,
+ yHandles,
+ yIndices,
+ xHandles,
+ xIndices,
+ dxHandles,
+ dxIndices);
int ndy = dyHandlesAndIndices.length >> 1;
if (ndy != dy.length) {
throw new IllegalStateException(String.valueOf(ndy) + " gradients were added to the graph when " + dy.length
@@ -207,16 +224,16 @@ public final class Graph implements AutoCloseable {
/**
* Adds operations to compute the partial derivatives of sum of {@code y}s w.r.t {@code x}s,
* i.e., {@code dy/dx_1, dy/dx_2...}
- * <p>
+ * <p>
* This is a simplified version of {@link #addGradients(Output[], Output[], Output[]) where {@code y} is
- * a single output and {@code dx} is null.
- *
+ * a single output, {@code dx} is null and {@code prefix} is null.
+ *
* @param y output of the function to derive
* @param x inputs of the function for which partial derivatives are computed
* @return the partial derivatives {@code dy} with the size of {@code x}
*/
public Output<?>[] addGradients(Output<?> y, Output<?>[] x) {
- return addGradients(new Output<?>[]{y}, x, null);
+ return addGradients(null, new Output<?>[] {y}, x, null);
}
private final Object nativeHandleLock = new Object();
@@ -330,8 +347,15 @@ public final class Graph implements AutoCloseable {
private static native byte[] toGraphDef(long handle);
- private static native long[] addGradients(long handle, long[] inputHandles, int[] inputIndices,
- long[] outputHandles, int[] outputIndices, long[] gradInputHandles, int[] gradInputIndices);
+ private static native long[] addGradients(
+ long handle,
+ String prefix,
+ long[] inputHandles,
+ int[] inputIndices,
+ long[] outputHandles,
+ int[] outputIndices,
+ long[] gradInputHandles,
+ int[] gradInputIndices);
static {
TensorFlow.init();
diff --git a/tensorflow/java/src/main/java/org/tensorflow/op/Scope.java b/tensorflow/java/src/main/java/org/tensorflow/op/Scope.java
index 8de2eaeb79..5a233bcc98 100644
--- a/tensorflow/java/src/main/java/org/tensorflow/op/Scope.java
+++ b/tensorflow/java/src/main/java/org/tensorflow/op/Scope.java
@@ -135,17 +135,8 @@ public final class Scope {
* }</pre>
*
* <p><b>Note:</b> if you provide a composite operator building class (i.e, a class that adds a
- * set of related operations to the graph by calling other operator building code) you should also
- * create a {@link #withSubScope(String)} scope for the underlying operators to group them under a
- * meaningful name.
- *
- * <pre>{@code
- * public static Stddev create(Scope scope, ...) {
- * // group sub-operations under a common name
- * Scope group = scope.withSubScope("stddev");
- * ... Sqrt.create(group, Mean.create(group, ...))
- * }
- * }</pre>
+ * set of related operations to the graph by calling other operator building code), the provided
+ * name will act as a subscope to all underlying operators.
*
* @param defaultName name for the underlying operator.
* @return unique name for the operator.
diff --git a/tensorflow/java/src/main/java/org/tensorflow/op/core/Gradients.java b/tensorflow/java/src/main/java/org/tensorflow/op/core/Gradients.java
index f4671c8af9..eea9dc1c47 100644
--- a/tensorflow/java/src/main/java/org/tensorflow/op/core/Gradients.java
+++ b/tensorflow/java/src/main/java/org/tensorflow/op/core/Gradients.java
@@ -18,7 +18,6 @@ package org.tensorflow.op.core;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
-
import org.tensorflow.Operand;
import org.tensorflow.Output;
import org.tensorflow.op.Op;
@@ -54,32 +53,36 @@ public class Gradients implements Op, Iterable<Operand<?>> {
* Optional attributes for {@link Gradients}
*/
public static class Options {
-
+
/**
* @param dx partial derivatives of some loss function {@code L} w.r.t. {@code y}
* @return this option builder
*/
- public Options dx(Iterable<Operand<?>> dx) {
+ public Options dx(Iterable<? extends Operand<?>> dx) {
this.dx = dx;
return this;
}
-
- private Iterable<Operand<?>> dx;
-
+
+ private Iterable<? extends Operand<?>> dx;
+
private Options() {
}
}
/**
* Adds gradients computation ops to the graph according to scope.
- *
+ *
* @param scope current graph scope
* @param y outputs of the function to derive
* @param x inputs of the function for which partial derivatives are computed
* @param options carries optional attributes values
* @return a new instance of {@code Gradients}
*/
- public static Gradients create(Scope scope, Iterable<Operand<?>> y, Iterable<Operand<?>> x, Options... options) {
+ public static Gradients create(
+ Scope scope,
+ Iterable<? extends Operand<?>> y,
+ Iterable<? extends Operand<?>> x,
+ Options... options) {
Output<?>[] dx = null;
if (options != null) {
for (Options opts : options) {
@@ -88,16 +91,20 @@ public class Gradients implements Op, Iterable<Operand<?>> {
}
}
}
- Output<?>[] gradOutputs = scope.graph().addGradients(Operands.asOutputs(y), Operands.asOutputs(x), dx);
- return new Gradients(Arrays.asList(gradOutputs));
+ Output<?>[] dy =
+ scope
+ .graph()
+ .addGradients(
+ scope.makeOpName("Gradients"), Operands.asOutputs(y), Operands.asOutputs(x), dx);
+ return new Gradients(Arrays.asList(dy));
}
/**
* Adds gradients computation ops to the graph according to scope.
- *
- * This is a simplified version of {@link #create(Scope, Iterable, Iterable, Options...)} where {@code y} is
- * a single output.
- *
+ *
+ * <p>This is a simplified version of {@link #create(Scope, Iterable, Iterable, Options...)} where
+ * {@code y} is a single output.
+ *
* @param scope current graph scope
* @param y output of the function to derive
* @param x inputs of the function for which partial derivatives are computed
@@ -105,7 +112,8 @@ public class Gradients implements Op, Iterable<Operand<?>> {
* @return a new instance of {@code Gradients}
*/
@SuppressWarnings({"unchecked", "rawtypes"})
- public static Gradients create(Scope scope, Operand<?> y, Iterable<Operand<?>> x, Options... options) {
+ public static Gradients create(
+ Scope scope, Operand<?> y, Iterable<? extends Operand<?>> x, Options... options) {
return create(scope, (Iterable) Arrays.asList(y), x, options);
}
@@ -113,7 +121,7 @@ public class Gradients implements Op, Iterable<Operand<?>> {
* @param dx partial derivatives of some loss function {@code L} w.r.t. {@code y}
* @return builder to add more options to this operation
*/
- public Options dx(Iterable<Operand<?>> dx) {
+ public static Options dx(Iterable<? extends Operand<?>> dx) {
return new Options().dx(dx);
}
@@ -129,13 +137,13 @@ public class Gradients implements Op, Iterable<Operand<?>> {
public List<Output<?>> dy() {
return dy;
}
-
+
/**
* Returns a symbolic handle to one of the gradient operation output
- * <p>
- * Warning: Does not check that the type of the tensor matches T. It is recommended to call
+ *
+ * <p>Warning: Does not check that the type of the tensor matches T. It is recommended to call
* this method with an explicit type parameter rather than letting it be inferred, e.g. {@code
- * gradients.<Integer>dy(0)}
+ * gradients.<Float>dy(0)}
*
* @param <T> The expected element type of the tensors produced by this output.
* @param index The index of the output among the gradients added by this operation
diff --git a/tensorflow/java/src/main/native/graph_jni.cc b/tensorflow/java/src/main/native/graph_jni.cc
index dac6a345e9..f1744d8769 100644
--- a/tensorflow/java/src/main/native/graph_jni.cc
+++ b/tensorflow/java/src/main/native/graph_jni.cc
@@ -133,12 +133,10 @@ Java_org_tensorflow_Graph_toGraphDef(JNIEnv* env, jclass clazz, jlong handle) {
return ret;
}
-JNIEXPORT jlongArray JNICALL
-Java_org_tensorflow_Graph_addGradients(JNIEnv* env, jclass clazz, jlong handle,
- jlongArray y_handles, jintArray y_indices,
- jlongArray x_handles, jintArray x_indices,
- jlongArray dx_handles, jintArray dx_indices) {
-
+JNIEXPORT jlongArray JNICALL Java_org_tensorflow_Graph_addGradients(
+ JNIEnv* env, jclass clazz, jlong handle, jstring prefix,
+ jlongArray y_handles, jintArray y_indices, jlongArray x_handles,
+ jintArray x_indices, jlongArray dx_handles, jintArray dx_indices) {
TF_Graph* g = requireHandle(env, handle);
if (g == nullptr) return nullptr;
@@ -163,9 +161,16 @@ Java_org_tensorflow_Graph_addGradients(JNIEnv* env, jclass clazz, jlong handle,
}
if (env->ExceptionCheck()) return nullptr;
+ const char* cprefix = nullptr;
+ if (prefix != nullptr) {
+ cprefix = env->GetStringUTFChars(prefix, nullptr);
+ }
TF_Status* status = TF_NewStatus();
- TF_AddGradients(g, y.get(), ny, x.get(), nx, dx.get(), status, dy.get());
-
+ TF_AddGradientsWithPrefix(g, cprefix, y.get(), ny, x.get(), nx, dx.get(),
+ status, dy.get());
+ if (prefix != nullptr) {
+ env->ReleaseStringUTFChars(prefix, cprefix);
+ }
if (!throwExceptionIfNotOK(env, status)) {
TF_DeleteStatus(status);
return nullptr;
diff --git a/tensorflow/java/src/main/native/graph_jni.h b/tensorflow/java/src/main/native/graph_jni.h
index 4f87e8d5a7..215695cdfd 100644
--- a/tensorflow/java/src/main/native/graph_jni.h
+++ b/tensorflow/java/src/main/native/graph_jni.h
@@ -76,11 +76,11 @@ JNIEXPORT jbyteArray JNICALL Java_org_tensorflow_Graph_toGraphDef(JNIEnv *,
/*
* Class: org_tensorflow_Graph
* Method: name
- * Signature: (J[J[I[J[I[J[I)[J
+ * Signature: (JLjava/lang/String;[J[I[J[I[J[I)[J
*/
-JNIEXPORT jlongArray JNICALL Java_org_tensorflow_Graph_addGradients(JNIEnv *,
- jclass, jlong, jlongArray, jintArray, jlongArray, jintArray, jlongArray,
- jintArray);
+JNIEXPORT jlongArray JNICALL Java_org_tensorflow_Graph_addGradients(
+ JNIEnv *, jclass, jlong, jstring, jlongArray, jintArray, jlongArray,
+ jintArray, jlongArray, jintArray);
#ifdef __cplusplus
} // extern "C"
diff --git a/tensorflow/java/src/test/java/org/tensorflow/GraphTest.java b/tensorflow/java/src/test/java/org/tensorflow/GraphTest.java
index c2e52c22c6..7c05c1deaf 100644
--- a/tensorflow/java/src/test/java/org/tensorflow/GraphTest.java
+++ b/tensorflow/java/src/test/java/org/tensorflow/GraphTest.java
@@ -22,7 +22,6 @@ import static org.junit.Assert.assertTrue;
import java.util.HashSet;
import java.util.Iterator;
-
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -180,8 +179,8 @@ public class GraphTest {
Output<Float> x = TestUtil.placeholder(g, "x", Float.class);
Output<Float> y0 = TestUtil.square(g, "y0", x);
Output<Float> y1 = TestUtil.square(g, "y1", y0);
-
- Output<?>[] grad = g.addGradients(toArray(y0, y1), toArray(x), null);
+
+ Output<?>[] grad = g.addGradients(null, toArray(y0, y1), toArray(x), null);
assertNotNull(grad);
assertEquals(1, grad.length);
assertEquals(DataType.FLOAT, grad[0].dataType());
@@ -212,7 +211,7 @@ public class GraphTest {
assertEquals(1, grad0.length);
assertEquals(DataType.FLOAT, grad0[0].dataType());
- Output<?>[] grad1 = g.addGradients(toArray(y0), toArray(x), toArray(grad0[0]));
+ Output<?>[] grad1 = g.addGradients(null, toArray(y0), toArray(x), toArray(grad0[0]));
assertNotNull(grad1);
assertEquals(1, grad1.length);
assertEquals(DataType.FLOAT, grad1[0].dataType());
@@ -228,6 +227,33 @@ public class GraphTest {
}
}
}
+
+ @Test
+ public void validateGradientsNames() {
+ try (Graph g = new Graph()) {
+
+ Output<Float> x = TestUtil.placeholder(g, "x", Float.class);
+ Output<Float> y0 = TestUtil.square(g, "y0", x);
+
+ Output<?>[] grad0 = g.addGradients(null, toArray(y0), toArray(x), null);
+ assertTrue(grad0[0].op().name().startsWith("gradients/"));
+
+ Output<?>[] grad1 = g.addGradients(null, toArray(y0), toArray(x), null);
+ assertTrue(grad1[0].op().name().startsWith("gradients_1/"));
+
+ Output<?>[] grad2 = g.addGradients("more_gradients", toArray(y0), toArray(x), null);
+ assertTrue(grad2[0].op().name().startsWith("more_gradients/"));
+
+ Output<?>[] grad3 = g.addGradients("even_more_gradients", toArray(y0), toArray(x), null);
+ assertTrue(grad3[0].op().name().startsWith("even_more_gradients/"));
+
+ try {
+ g.addGradients("even_more_gradients", toArray(y0), toArray(x), null);
+ } catch (IllegalArgumentException e) {
+ // expected exception
+ }
+ }
+ }
private static Output<?>[] toArray(Output<?>... outputs) {
return outputs;
diff --git a/tensorflow/java/src/test/java/org/tensorflow/TestUtil.java b/tensorflow/java/src/test/java/org/tensorflow/TestUtil.java
index 4e84886416..f984c508ee 100644
--- a/tensorflow/java/src/test/java/org/tensorflow/TestUtil.java
+++ b/tensorflow/java/src/test/java/org/tensorflow/TestUtil.java
@@ -24,7 +24,7 @@ public class TestUtil {
public static final class AutoCloseableList<E extends AutoCloseable> extends ArrayList<E>
implements AutoCloseable {
- AutoCloseableList(Collection<? extends E> c) {
+ public AutoCloseableList(Collection<? extends E> c) {
super(c);
}
diff --git a/tensorflow/java/src/test/java/org/tensorflow/op/core/GradientsTest.java b/tensorflow/java/src/test/java/org/tensorflow/op/core/GradientsTest.java
new file mode 100644
index 0000000000..3f49790b29
--- /dev/null
+++ b/tensorflow/java/src/test/java/org/tensorflow/op/core/GradientsTest.java
@@ -0,0 +1,131 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+package org.tensorflow.op.core;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.tensorflow.Graph;
+import org.tensorflow.Output;
+import org.tensorflow.Session;
+import org.tensorflow.Tensor;
+import org.tensorflow.Tensors;
+import org.tensorflow.TestUtil;
+import org.tensorflow.op.Scope;
+
+@RunWith(JUnit4.class)
+public class GradientsTest {
+
+ @Test
+ public void createGradients() {
+ try (Graph g = new Graph();
+ Session sess = new Session(g)) {
+ Scope scope = new Scope(g);
+
+ Output<Float> x = TestUtil.placeholder(g, "x1", Float.class);
+ Output<Float> y0 = TestUtil.square(g, "y0", x);
+ Output<Float> y1 = TestUtil.square(g, "y1", y0);
+
+ Gradients grads = Gradients.create(scope, y1, Arrays.asList(x, y0));
+
+ assertNotNull(grads);
+ assertNotNull(grads.dy());
+ assertEquals(2, grads.dy().size());
+
+ try (Tensor<Float> c = Tensors.create(3.0f);
+ TestUtil.AutoCloseableList<Tensor<?>> outputs =
+ new TestUtil.AutoCloseableList<>(
+ sess.runner().feed(x, c).fetch(grads.dy(0)).fetch(grads.dy(1)).run())) {
+
+ assertEquals(108.0f, outputs.get(0).floatValue(), 0.0f);
+ assertEquals(18.0f, outputs.get(1).floatValue(), 0.0f);
+ }
+ }
+ }
+
+ @Test
+ public void createGradientsWithSum() {
+ try (Graph g = new Graph();
+ Session sess = new Session(g)) {
+ Scope scope = new Scope(g);
+
+ Output<Float> x = TestUtil.placeholder(g, "x1", Float.class);
+ Output<Float> y0 = TestUtil.square(g, "y0", x);
+ Output<Float> y1 = TestUtil.square(g, "y1", y0);
+
+ Gradients grads = Gradients.create(scope, Arrays.asList(y0, y1), Arrays.asList(x));
+
+ assertNotNull(grads);
+ assertNotNull(grads.dy());
+ assertEquals(1, grads.dy().size());
+
+ try (Tensor<Float> c = Tensors.create(3.0f);
+ TestUtil.AutoCloseableList<Tensor<?>> outputs =
+ new TestUtil.AutoCloseableList<>(sess.runner().feed(x, c).fetch(grads.dy(0)).run())) {
+
+ assertEquals(114.0f, outputs.get(0).floatValue(), 0.0f);
+ }
+ }
+ }
+
+ @Test
+ public void createGradientsWithInitialValues() {
+ try (Graph g = new Graph();
+ Session sess = new Session(g)) {
+ Scope scope = new Scope(g);
+
+ Output<Float> x = TestUtil.placeholder(g, "x1", Float.class);
+ Output<Float> y0 = TestUtil.square(g, "y0", x);
+ Output<Float> y1 = TestUtil.square(g, "y1", y0);
+
+ Gradients grads0 = Gradients.create(scope, y1, Arrays.asList(y0));
+ Gradients grads1 = Gradients.create(scope, y0, Arrays.asList(x), Gradients.dx(grads0.dy()));
+
+ assertNotNull(grads1);
+ assertNotNull(grads1.dy());
+ assertEquals(1, grads1.dy().size());
+
+ try (Tensor<Float> c = Tensors.create(3.0f);
+ TestUtil.AutoCloseableList<Tensor<?>> outputs =
+ new TestUtil.AutoCloseableList<>(
+ sess.runner().feed(x, c).fetch(grads1.dy(0)).run())) {
+
+ assertEquals(108.0f, outputs.get(0).floatValue(), 0.0f);
+ }
+ }
+ }
+
+ @Test
+ public void validateGradientsNames() {
+ try (Graph g = new Graph()) {
+ Scope scope = new Scope(g).withSubScope("sub");
+
+ Output<Float> x = TestUtil.placeholder(g, "x1", Float.class);
+ Output<Float> y = TestUtil.square(g, "y", x);
+
+ Gradients grad0 = Gradients.create(scope, y, Arrays.asList(x));
+ assertTrue(grad0.dy(0).op().name().startsWith("sub/Gradients/"));
+
+ Gradients grad1 = Gradients.create(scope.withName("MyGradients"), y, Arrays.asList(x));
+ assertTrue(grad1.dy(0).op().name().startsWith("sub/MyGradients/"));
+ }
+ }
+}
diff --git a/tensorflow/python/BUILD b/tensorflow/python/BUILD
index 62020d3de2..83b82aa0cc 100644
--- a/tensorflow/python/BUILD
+++ b/tensorflow/python/BUILD
@@ -3659,6 +3659,7 @@ tf_cuda_library(
"//tensorflow/core:graph",
"//tensorflow/core:lib",
"//tensorflow/core:protos_all_cc",
+ "//tensorflow/core:session_ref",
"//third_party/py/numpy:headers",
"//third_party/python_runtime:headers",
],
diff --git a/tensorflow/python/client/session.py b/tensorflow/python/client/session.py
index 180bb74d00..58a002c776 100644
--- a/tensorflow/python/client/session.py
+++ b/tensorflow/python/client/session.py
@@ -29,6 +29,7 @@ import numpy as np
from tensorflow.core.protobuf import config_pb2
from tensorflow.python import pywrap_tensorflow as tf_session
from tensorflow.python.framework import device
+from tensorflow.python.framework import error_interpolation
from tensorflow.python.framework import errors
from tensorflow.python.framework import ops
from tensorflow.python.framework import sparse_tensor
@@ -630,7 +631,7 @@ class BaseSession(SessionInterface):
opts = tf_session.TF_NewSessionOptions(target=self._target, config=config)
try:
# pylint: disable=protected-access
- self._session = tf_session.TF_NewSession(self._graph._c_graph, opts)
+ self._session = tf_session.TF_NewSessionRef(self._graph._c_graph, opts)
# pylint: enable=protected-access
finally:
tf_session.TF_DeleteSessionOptions(opts)
@@ -1235,8 +1236,12 @@ class BaseSession(SessionInterface):
return _fetch_handler_run
- # Captures the name of a node in an error status.
- _NODEDEF_NAME_RE = re.compile(r'\[\[Node: ([^ ]*?) =')
+ # Captures the name of a node in an error status. The regex below matches
+ # both the old and the new formats:
+ # Old format: [[Node: <node_name> = ...]]
+ # New format: [[{{node <node_name>}} = ...]]
+ _NODEDEF_NAME_RE = re.compile(
+ r'\[\[(Node: )?(\{\{node )?([^\} ]*)(\}\})?\s*=')
def _do_run(self, handle, target_list, fetch_list, feed_dict, options,
run_metadata):
@@ -1291,12 +1296,15 @@ class BaseSession(SessionInterface):
node_def = None
op = None
if m is not None:
- node_name = m.group(1)
+ node_name = m.group(3)
try:
op = self._graph.get_operation_by_name(node_name)
node_def = op.node_def
except KeyError:
pass
+ if (self._config is not None and
+ self._config.experimental.client_handles_error_formatting):
+ message = error_interpolation.interpolate(message, self._graph)
raise type(e)(node_def, op, message)
def _extend_graph(self):
diff --git a/tensorflow/python/client/tf_session.i b/tensorflow/python/client/tf_session.i
index 1cdd8e0b6a..39a2922ac0 100644
--- a/tensorflow/python/client/tf_session.i
+++ b/tensorflow/python/client/tf_session.i
@@ -777,6 +777,7 @@ def TF_Reset(target, containers=None, config=None):
$1 = &types_local;
}
+%unignore TF_NewSessionRef;
%unignore SetRequireShapeInferenceFns;
%unignore TF_TryEvaluateConstant_wrapper;
%noexception TF_TryEvaluateConstant_wrapper;
diff --git a/tensorflow/python/client/tf_session_helper.cc b/tensorflow/python/client/tf_session_helper.cc
index b6481e7e29..bcd4af2912 100644
--- a/tensorflow/python/client/tf_session_helper.cc
+++ b/tensorflow/python/client/tf_session_helper.cc
@@ -20,6 +20,7 @@ limitations under the License.
#include "tensorflow/c/c_api.h"
#include "tensorflow/c/c_api_internal.h"
#include "tensorflow/c/tf_status_helper.h"
+#include "tensorflow/core/common_runtime/session_ref.h"
#include "tensorflow/core/framework/allocator.h"
#include "tensorflow/core/framework/attr_value.pb.h"
#include "tensorflow/core/framework/attr_value_util.h"
@@ -42,6 +43,19 @@ static const char* kFeedDictErrorMsg =
"feed_dict must be a dictionary mapping strings to NumPy arrays.";
} // end namespace
+TF_Session* TF_NewSessionRef(TF_Graph* graph, const TF_SessionOptions* opts,
+ TF_Status* status) {
+ TF_Session* tf_session = TF_NewSession(graph, opts, status);
+ if (tf_session == nullptr) {
+ return nullptr;
+ }
+
+ Session* session = reinterpret_cast<Session*>(tf_session->session);
+ SessionRef* session_ref = new SessionRef(session);
+ tf_session->session = session_ref;
+ return tf_session;
+}
+
void TF_Run_wrapper_helper(TF_DeprecatedSession* session, const char* handle,
const TF_Buffer* run_options, PyObject* feed_dict,
const NameVector& output_names,
diff --git a/tensorflow/python/client/tf_session_helper.h b/tensorflow/python/client/tf_session_helper.h
index cfd27c2bee..dab7e71aac 100644
--- a/tensorflow/python/client/tf_session_helper.h
+++ b/tensorflow/python/client/tf_session_helper.h
@@ -40,6 +40,9 @@ typedef tensorflow::gtl::InlinedVector<PyObject*, 8> PyObjectVector;
// A TF_TensorVector is a vector of borrowed pointers to TF_Tensors.
typedef gtl::InlinedVector<TF_Tensor*, 8> TF_TensorVector;
+TF_Session* TF_NewSessionRef(TF_Graph* graph, const TF_SessionOptions* opts,
+ TF_Status* status);
+
// Run the graph associated with the session starting with the
// supplied inputs[]. Regardless of success or failure, inputs[] are
// stolen by the implementation (i.e. the implementation will
diff --git a/tensorflow/python/data/kernel_tests/BUILD b/tensorflow/python/data/kernel_tests/BUILD
index 38505c0a01..b66b87ce6c 100644
--- a/tensorflow/python/data/kernel_tests/BUILD
+++ b/tensorflow/python/data/kernel_tests/BUILD
@@ -318,7 +318,7 @@ tf_py_test(
],
)
-tf_py_test(
+cuda_py_test(
name = "iterator_ops_test",
size = "small",
srcs = ["iterator_ops_test.py"],
diff --git a/tensorflow/python/data/ops/iterator_ops.py b/tensorflow/python/data/ops/iterator_ops.py
index 3ef22cf981..494df178df 100644
--- a/tensorflow/python/data/ops/iterator_ops.py
+++ b/tensorflow/python/data/ops/iterator_ops.py
@@ -57,6 +57,13 @@ GET_NEXT_CALL_WARNING_MESSAGE = (
GLOBAL_ITERATORS = "iterators"
+def _device_stack_is_empty():
+ # pylint: disable=protected-access
+ device_stack = ops.get_default_graph()._device_functions_outer_to_inner
+ # pylint: enable=protected-access
+ return not bool(device_stack)
+
+
@tf_export("data.Iterator")
class Iterator(object):
"""Represents the state of iterating through a `Dataset`."""
@@ -174,7 +181,7 @@ class Iterator(object):
if shared_name is None:
shared_name = ""
if compat.forward_compatible(2018, 8, 3):
- if not ops.get_default_graph()._graph_device_function_stack: # pylint: disable=protected-access
+ if _device_stack_is_empty():
with ops.device("/cpu:0"):
iterator_resource = gen_dataset_ops.iterator_v2(
container="",
@@ -263,7 +270,7 @@ class Iterator(object):
nest.assert_same_structure(output_types, output_shapes)
string_handle = ops.convert_to_tensor(string_handle, dtype=dtypes.string)
if compat.forward_compatible(2018, 8, 3):
- if not ops.get_default_graph()._graph_device_function_stack: # pylint: disable=protected-access
+ if _device_stack_is_empty():
with ops.device("/cpu:0"):
iterator_resource = gen_dataset_ops.iterator_from_string_handle_v2(
string_handle,
diff --git a/tensorflow/python/distribute/BUILD b/tensorflow/python/distribute/BUILD
new file mode 100644
index 0000000000..2bd0b4320a
--- /dev/null
+++ b/tensorflow/python/distribute/BUILD
@@ -0,0 +1,43 @@
+package(
+ default_visibility = ["//tensorflow:internal"],
+)
+
+licenses(["notice"]) # Apache 2.0
+
+exports_files(["LICENSE"])
+
+load("//tensorflow:tensorflow.bzl", "py_test")
+
+py_library(
+ name = "distribute_coordinator",
+ srcs = [
+ "distribute_coordinator.py",
+ ],
+ srcs_version = "PY2AND3",
+ deps = [
+ "//tensorflow/core:protos_all_py",
+ "//tensorflow/python:training",
+ ],
+)
+
+py_test(
+ name = "distribute_coordinator_test",
+ size = "small",
+ srcs = ["distribute_coordinator_test.py"],
+ srcs_version = "PY2AND3",
+ tags = ["no_pip"],
+ deps = [
+ ":distribute_coordinator",
+ "//tensorflow/core:protos_all_py",
+ "//tensorflow/python:client_testlib",
+ "//tensorflow/python:control_flow_ops",
+ "//tensorflow/python:distributed_framework_test_lib",
+ "//tensorflow/python:framework_ops",
+ "//tensorflow/python:framework_test_lib",
+ "//tensorflow/python:math_ops",
+ "//tensorflow/python:session",
+ "//tensorflow/python:training",
+ "//tensorflow/python:variable_scope",
+ "//tensorflow/python:variables",
+ ],
+)
diff --git a/tensorflow/python/distribute/distribute_coordinator.py b/tensorflow/python/distribute/distribute_coordinator.py
new file mode 100644
index 0000000000..04c50dbafc
--- /dev/null
+++ b/tensorflow/python/distribute/distribute_coordinator.py
@@ -0,0 +1,361 @@
+# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""A unified and split coordinator for distributed TensorFlow."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import copy
+import json
+import os
+import threading
+
+from tensorflow.core.protobuf import cluster_pb2
+from tensorflow.python.training import server_lib
+
+
+class _TaskType(object):
+ PS = "ps"
+ WORKER = "worker"
+ CHIEF = "chief"
+ EVALUATOR = "evaluator"
+
+
+_coordinator_context = threading.local()
+
+
+def get_current_coordinator_context():
+ """Returns the current coordinator context."""
+ try:
+ return _coordinator_context.current
+ except AttributeError:
+ return None
+
+
+class _Barrier(object):
+ """A reusable barrier class for worker synchronization."""
+
+ def __init__(self, num_participants):
+ """Initializes the barrier object.
+
+ Args:
+ num_participants: an integer which is the expected number of calls of
+ `wait` pass to through this barrier.
+ """
+ self._num_participants = num_participants
+ self._counter = 0
+ self._flag = False
+ self._local_sense = threading.local()
+ self._lock = threading.Lock()
+ self._condition = threading.Condition()
+
+ def wait(self):
+ """Waits until all other callers reach the same wait call."""
+ if not hasattr(self._local_sense, "value"):
+ self._local_sense.value = False
+ self._local_sense.value = not self._flag
+ with self._lock:
+ self._counter += 1
+ if self._counter == self._num_participants:
+ self._counter = 0
+ self._flag = self._local_sense.value
+ with self._condition:
+ while self._flag != self._local_sense.value:
+ self._condition.wait()
+ self._condition.notify_all()
+
+
+def _get_num_workers(cluster_spec):
+ """Gets number of workers including chief."""
+ if not cluster_spec:
+ return 0
+ return len(cluster_spec.as_dict().get(_TaskType.WORKER, [])) + len(
+ cluster_spec.as_dict().get(_TaskType.CHIEF, []))
+
+
+class _CoordinatorContext(object):
+ """The coordinator context class.
+
+ This context object provides configuration information for each task. One
+ context manager with a coordinator context object will be created per
+ invocation to the `worker_fn` where `get_current_coordinator_context` can be
+ called to access the coordinator context object.
+ """
+
+ def __init__(self,
+ cluster_spec,
+ task_type,
+ task_id,
+ between_graph=False,
+ rpc_layer="grpc",
+ worker_barrier=None):
+ """Initialize the coordinator context object.
+
+ Args:
+ cluster_spec: a ClusterSpec object. It can be empty or None in the local
+ training case.
+ task_type: a string indicating the role of the corresponding task, such as
+ "worker" or "ps". It can be None if it is local training or
+ `between_graph` is False.
+ task_id: an integer indicating id of the corresponding task. It can be
+ None if it is local training or `between_graph` is False.
+ between_graph: whether it is between-graph replication or not.
+ rpc_layer: optional string specifying the RPC protocol for communication
+ with worker masters. If None or empty, hosts in the `cluster_spec` will
+ be used directly.
+ worker_barrier: optional, the barrier object for worker synchronization.
+
+ Raises:
+ ValueError: if task_type or task_id is Node or empty and it is distributed
+ between-graph replicated training.
+ """
+ if cluster_spec and between_graph:
+ if not task_type or task_id is None:
+ raise ValueError("`task_type` and `task_id` must be set in the "
+ "distributed between-graph replicated training.")
+ if task_type not in cluster_spec.jobs:
+ raise ValueError("`task_type` %r not found in the `cluster_spec` %r" %
+ (task_type, cluster_spec))
+ self._cluster_spec = cluster_spec
+ self._task_type = task_type
+ self._task_id = task_id
+ self._worker_barrier = worker_barrier
+ self._rpc_layer = rpc_layer
+ self._master_target = self._get_master_target()
+ self._num_workers = _get_num_workers(cluster_spec)
+ self._is_chief_node = self._is_chief()
+
+ def __enter__(self):
+ old_context = get_current_coordinator_context()
+ if old_context:
+ raise ValueError(
+ "You cannot run distribute coordinator in a `worker_fn`.")
+ _coordinator_context.current = self
+
+ def __exit__(self, unused_exception_type, unused_exception_value,
+ unused_traceback):
+ _coordinator_context.current = None
+
+ def _get_master_target(self):
+ """Return the master target for a task."""
+ # If cluster_spec is None or empty, we use local master.
+ if not self._cluster_spec:
+ return "local"
+
+ # If task_type is None, then it is in-graph replicated training. In this
+ # case we use the chief or first worker's master target.
+ if not self._task_type:
+ if _TaskType.CHIEF in self._cluster_spec.jobs:
+ assert not self.between_graph
+ task_type = _TaskType.CHIEF
+ task_id = 0
+ else:
+ assert _TaskType.WORKER in self._cluster_spec.jobs
+ task_type = _TaskType.WORKER
+ task_id = 0
+ else:
+ task_type = self._task_type
+ task_id = self._task_id
+
+ prefix = ""
+ if self._rpc_layer:
+ prefix = self._rpc_layer + "://"
+ return prefix + self._cluster_spec.job_tasks(task_type)[task_id or 0]
+
+ def _is_chief(self):
+ """Return whether the task is the chief worker."""
+ if (not self._cluster_spec or self._task_type in [_TaskType.CHIEF, None]):
+ return True
+
+ # If not local and chief not in the cluster_spec, use the first worker as
+ # chief.
+ if (_TaskType.CHIEF not in self._cluster_spec.jobs and
+ self._task_type == _TaskType.WORKER and self._task_id == 0):
+ return True
+ return False
+
+ def wait_for_other_workers(self):
+ """Waits for other workers to reach the same call to this method.
+
+ Raises:
+ ValueError: if `worker_barrier` is not passed to the __init__ method.
+ """
+ if not self._worker_barrier:
+ raise ValueError(
+ "`worker_barrier is not set in the coordinator context.`")
+ self._worker_barrier.wait()
+
+ @property
+ def distributed_mode(self):
+ """Whether it is distributed training or not."""
+ return bool(self._cluster_spec)
+
+ @property
+ def cluster_spec(self):
+ """Returns a copy of the cluster_spec object."""
+ return copy.deepcopy(self._cluster_spec)
+
+ @property
+ def task_type(self):
+ """Returns the role of the corresponing task."""
+ return self._task_type
+
+ @property
+ def task_id(self):
+ """Returns the id or index of the corresponing task."""
+ return self._task_id
+
+ @property
+ def master_target(self):
+ """Returns the session master for the corresponding task to connect to."""
+ return self._master_target
+
+ @property
+ def is_chief(self):
+ """Returns whether the task is a chief node."""
+ return self._is_chief_node
+
+ @property
+ def num_workers(self):
+ """Returns number of workers in the cluster, including chief."""
+ return self._num_workers
+
+
+def _run(worker_fn, cluster_spec, task_type, task_id, between_graph, rpc_layer,
+ worker_barrier):
+ with _CoordinatorContext(cluster_spec, task_type, task_id, between_graph,
+ rpc_layer, worker_barrier):
+ worker_fn()
+
+
+def run_distribute_coordinator(worker_fn,
+ cluster_spec=None,
+ between_graph=False,
+ rpc_layer=None):
+ """Run the coordinator for distributed TensorFlow.
+
+ This function runs a unified and split coordinator for distributed TensorFlow.
+ Given a `cluster_spec` specifying server addresses and their roles in a
+ cluster, this coordinator will figure out how to set them up, give the
+ underlying function the right targets for master sessions and coordinate their
+ training.
+
+ In addition to be the distribute coordinator, this is also the source of
+ configurations for each job in the distributed training. As there are multiple
+ ways to configure a distributed TensorFlow cluster, its context object
+ provides these configurations so that users or higher-level APIs don't have to
+ figure out the configuration for each job by themselves.
+
+ In the between-graph replicated training, this coordinator will create
+ multiple threads and each calls the `worker_fn` which is supposed to create
+ its own graph and connect to one worker master given by its coordinator
+ context. In the in-graph replicated training, it has only one thread calling
+ this `worker_fn`.
+
+ The `worker_fn` defines the training logic and is called under a its own
+ coordinator context which can be accessed to via
+ `get_current_coordinator_context`. A coordinator context provides access to
+ configurations for each task, e.g. the task_type, task_id, master target and
+ so on. Since `worker_fn` will be called in a thread and possibly multiple
+ times, caller should be careful when it accesses global data. For example, it
+ is unsafe to define flags in a `worker_fn` or to define different environment
+ variables for different `worker_fn`s.
+
+ The `worker_fn` for the between-graph replication is defined as if there are
+ only one worker corresponding to the `worker_fn` and possibly ps jobs. It
+ assigns variables to parameter servers and all other operations to that
+ worker. In the in-graph replication case, the `worker_fn` has to define
+ operations for all worker jobs. Using a distribution strategy can simplify the
+ `worker_fn` by not having to worry about the replication and device assignment
+ of variables and operations.
+
+ This method is intended to be invoked by high-level APIs so that users don't
+ have to explictly call it to run this coordinator. For those who don't use
+ high-level APIs, to change a program to use this coordinator, wrap everything
+ in a the program after global data definitions such as commandline flag
+ definition into the `worker_fn` and get task-specific configurations from
+ the coordinator context.
+
+ The `cluster_spec` can be either passed by the argument or parsed from the
+ "TF_CONFIG" envrionment variable. Example of a TF_CONFIG:
+ ```
+ cluster = {'chief': ['host0:2222'],
+ 'ps': ['host1:2222', 'host2:2222'],
+ 'worker': ['host3:2222', 'host4:2222', 'host5:2222']}
+ os.environ['TF_CONFIG'] = json.dumps({'cluster': cluster})
+ ```
+
+ If `cluster_spec` is not given in any format, it becomes local training and
+ this coordinator will connect to a local session.
+
+ For evaluation, if "evaluator" exist in the cluster_spec, a separate thread
+ will be created with its `task_type` set to "evaluator". If "evaluator" is not
+ set in the cluster_spec, it entirely depends on the `worker_fn` for how to do
+ evaluation.
+
+ Args:
+ worker_fn: the function to be called and given the access to a coordinator
+ context object.
+ cluster_spec: a dict, ClusterDef or ClusterSpec specifying servers and roles
+ in a cluster. If not set or empty, fall back to local training.
+ between_graph: a boolean. It is only useful when `cluster_spec` is set and
+ not empty. If true, it will use between-graph replicated training;
+ otherwise it will use in-graph replicated training.
+ rpc_layer: optional string, the protocol for RPC, e.g. "grpc".
+
+ Raises:
+ ValueError: if `cluster_spec` is supplied but not a dict or a ClusterDef or
+ a ClusterSpec.
+ """
+ if not cluster_spec:
+ tf_config = json.loads(os.environ.get("TF_CONFIG", "{}"))
+ cluster_spec = tf_config.get("cluster", {})
+
+ if cluster_spec:
+ if isinstance(cluster_spec, (dict, cluster_pb2.ClusterDef)):
+ cluster_spec = server_lib.ClusterSpec(cluster_spec)
+ elif not isinstance(cluster_spec, server_lib.ClusterSpec):
+ raise ValueError(
+ "`cluster_spec' should be dict or a `tf.train.ClusterSpec` or a "
+ "`tf.train.ClusterDef` object")
+ # TODO(yuefengz): validate cluster_spec.
+
+ threads = []
+ if cluster_spec and _TaskType.EVALUATOR in cluster_spec.jobs:
+ t = threading.Thread(
+ target=_run,
+ args=(worker_fn, cluster_spec, _TaskType.EVALUATOR, 0, between_graph,
+ rpc_layer, None))
+ t.start()
+ threads.append(t)
+
+ if cluster_spec and between_graph:
+ worker_barrier = _Barrier(_get_num_workers(cluster_spec))
+ for task_type in [_TaskType.CHIEF, _TaskType.WORKER]:
+ for task_id in range(len(cluster_spec.as_dict().get(task_type, []))):
+ t = threading.Thread(
+ target=_run,
+ args=(worker_fn, cluster_spec, task_type, task_id, between_graph,
+ rpc_layer, worker_barrier))
+ t.start()
+ threads.append(t)
+ else:
+ # Local or in-graph replicated training.
+ _run(worker_fn, cluster_spec, None, None, between_graph, rpc_layer, None)
+
+ # TODO(yuefengz): wrapper threads into thread coordinator?
+ for t in threads:
+ t.join()
diff --git a/tensorflow/python/distribute/distribute_coordinator_test.py b/tensorflow/python/distribute/distribute_coordinator_test.py
new file mode 100644
index 0000000000..82fd823352
--- /dev/null
+++ b/tensorflow/python/distribute/distribute_coordinator_test.py
@@ -0,0 +1,291 @@
+# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ==============================================================================
+"""Tests for distribute coordinator."""
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import contextlib
+import copy
+import threading
+import six
+
+from tensorflow.core.protobuf import config_pb2
+from tensorflow.python.client import session
+from tensorflow.python.distribute import distribute_coordinator
+from tensorflow.python.framework import ops
+from tensorflow.python.framework import test_util
+from tensorflow.python.ops import control_flow_ops
+from tensorflow.python.ops import math_ops
+from tensorflow.python.ops import variable_scope
+from tensorflow.python.ops import variables
+from tensorflow.python.platform import test
+
+CHIEF = distribute_coordinator._TaskType.CHIEF
+WORKER = distribute_coordinator._TaskType.WORKER
+PS = distribute_coordinator._TaskType.PS
+EVALUATOR = distribute_coordinator._TaskType.EVALUATOR
+
+NUM_WORKERS = 3
+NUM_PS = 2
+
+
+def _bytes_to_str(maybe_bytes):
+ if isinstance(maybe_bytes, six.string_types):
+ return maybe_bytes
+ else:
+ return str(maybe_bytes, "utf-8")
+
+
+class DistributeCoordinatorTest(test.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ # We have to create a global in-process cluster because once an in-process
+ # tensorflow server is created, there is no way to terminate it. Please see
+ # multi_worker_test_base.py for more details.
+ cls._workers, cls._ps = test_util.create_local_cluster(
+ NUM_WORKERS, num_ps=NUM_PS)
+ cls._cluster_spec = {
+ WORKER: [_bytes_to_str(w.target) for w in cls._workers],
+ PS: [_bytes_to_str(ps.target) for ps in cls._ps]
+ }
+
+ def setUp(self):
+ self._result_correct = 0
+ self._lock = threading.Lock()
+ self._task_context = {}
+
+ @contextlib.contextmanager
+ def _test_session(self, target):
+ config = config_pb2.ConfigProto(allow_soft_placement=True)
+ config.graph_options.optimizer_options.opt_level = -1
+ with session.Session(graph=None, config=config, target=target) as sess:
+ yield sess
+
+ def _in_graph_worker_fn(self):
+ context = distribute_coordinator.get_current_coordinator_context()
+ self.assertTrue(context is not None)
+ with self._test_session(target=context.master_target) as sess:
+ xs = []
+ expected = 0.0
+ for i in range(context.num_workers):
+ with ops.device("/job:worker/task:%d" % i):
+ x = variable_scope.get_variable("x_%d" % i, initializer=10.0)
+ x_add = x.assign_add(float(i))
+ xs.append(x_add)
+ expected += i + 10.0
+
+ with ops.device("/job:worker/task:0"):
+ result = math_ops.add_n(xs)
+
+ variables.global_variables_initializer().run()
+ result_value = sess.run(result)
+ self.assertEqual(result_value, expected)
+ if result_value == expected:
+ self._result_correct += 1
+
+ def testInGraph(self):
+ """Test it runs in-graph replicated training correctly."""
+ distribute_coordinator.run_distribute_coordinator(
+ self._in_graph_worker_fn,
+ cluster_spec=self._cluster_spec,
+ between_graph=False)
+ self.assertEqual(self._result_correct, 1)
+
+ def _between_graph_worker_fn(self):
+ context = distribute_coordinator.get_current_coordinator_context()
+ self.assertTrue(context is not None)
+ with self._test_session(target=context.master_target) as sess:
+ with ops.device("/job:ps/task:0"):
+ # TODO(yuefengz): investigate why not using resource variable will make
+ # the test flaky.
+ x = variable_scope.get_variable(
+ "x", initializer=10.0, use_resource=True)
+ with ops.device("/job:ps/task:1"):
+ y = variable_scope.get_variable(
+ "y", initializer=20.0, use_resource=True)
+
+ x_add = x.assign_add(2.0)
+ y_sub = y.assign_sub(2.0)
+ train_op = control_flow_ops.group([x_add, y_sub])
+
+ if context.is_chief:
+ variables.global_variables_initializer().run()
+
+ # Synchronize workers after initializaton.
+ context.wait_for_other_workers()
+
+ sess.run(train_op)
+
+ # Synchronize workers after one step to make sure they all have finished
+ # training.
+ context.wait_for_other_workers()
+
+ x_val, y_val = sess.run([x, y])
+
+ self.assertEqual(x_val, 16.0)
+ self.assertEqual(y_val, 14.0)
+ if x_val == 16.0 and y_val == 14.0:
+ with self._lock:
+ self._result_correct += 1
+
+ def testBetweenGraph(self):
+ """Test it runs between-graph replicated training correctly."""
+ distribute_coordinator.run_distribute_coordinator(
+ self._between_graph_worker_fn,
+ cluster_spec=self._cluster_spec,
+ between_graph=True)
+
+ # Each finished worker will increment self._result_correct.
+ self.assertEqual(self._result_correct, NUM_WORKERS)
+
+ def _dump_task_context(self):
+ """Dumps the propoerties of each coordinator context.
+
+ It dumps the context properties to a dict mapping from task_type to a list
+ of tuples of master_target, num_workers, is_chief and distribute_mode, where
+ the list is indexed by the task_id.
+ """
+ context = distribute_coordinator.get_current_coordinator_context()
+ self.assertTrue(context is not None)
+ task_type = str(context.task_type)
+ task_id = context.task_id or 0
+ with self._lock:
+ if task_type not in self._task_context:
+ self._task_context[task_type] = []
+ while len(self._task_context[task_type]) <= task_id:
+ self._task_context[task_type].append(None)
+ self._task_context[task_type][task_id] = (context.master_target,
+ context.num_workers,
+ context.is_chief,
+ context.distributed_mode)
+
+ def testBetweenGraphContext(self):
+ # Dumps the task contexts to the self._task_context dict.
+ distribute_coordinator.run_distribute_coordinator(
+ self._dump_task_context,
+ cluster_spec=self._cluster_spec,
+ between_graph=True)
+
+ # There is only one type of task and there three such tasks.
+ self.assertEqual(len(self._task_context), 1)
+ self.assertTrue(WORKER in self._task_context)
+ self.assertEqual(len(self._task_context[WORKER]), NUM_WORKERS)
+
+ # Check whether each task has the right master_target, num_workers, is_chief
+ # and distributed_mode.
+ self.assertEqual(
+ self._task_context[WORKER][0],
+ (_bytes_to_str(self._workers[0].target), NUM_WORKERS, True, True))
+ self.assertEqual(
+ self._task_context[WORKER][1],
+ (_bytes_to_str(self._workers[1].target), NUM_WORKERS, False, True))
+ self.assertEqual(
+ self._task_context[WORKER][2],
+ (_bytes_to_str(self._workers[2].target), NUM_WORKERS, False, True))
+
+ def testInGraphContext(self):
+ # Dumps the task contexts to the self._task_context dict.
+ distribute_coordinator.run_distribute_coordinator(
+ self._dump_task_context,
+ cluster_spec=self._cluster_spec,
+ between_graph=False)
+
+ # There is only a "None" task in the dumped task context.
+ self.assertEqual(len(self._task_context), 1)
+ self.assertTrue("None" in self._task_context)
+ self.assertEqual(len(self._task_context["None"]), 1)
+
+ # Check whether each task has the right master_target, num_workers, is_chief
+ # and distributed_mode.
+ self.assertEqual(
+ self._task_context["None"][0],
+ (_bytes_to_str(self._workers[0].target), NUM_WORKERS, True, True))
+
+ def testLocalContext(self):
+ # Dumps the task contexts to the self._task_context dict.
+ distribute_coordinator.run_distribute_coordinator(
+ self._dump_task_context, cluster_spec=None, between_graph=True)
+
+ # There is only a "None" task.
+ self.assertEqual(len(self._task_context), 1)
+ self.assertTrue("None" in self._task_context)
+ self.assertEqual(len(self._task_context["None"]), 1)
+
+ # Check whether each task has the right master_target, num_workers, is_chief
+ # and distributed_mode.
+ self.assertEqual(self._task_context["None"][0], ("local", 0, True, False))
+
+ def testBetweenGraphContextWithChief(self):
+ # Adds a chief node, so there are NUM_WORKERS + 1 workers in total.
+ cluster_spec = copy.deepcopy(self._cluster_spec)
+ cluster_spec[CHIEF] = ["fake_chief"]
+
+ # Dumps the task contexts to the self._task_context dict.
+ distribute_coordinator.run_distribute_coordinator(
+ self._dump_task_context,
+ cluster_spec=cluster_spec,
+ between_graph=True,
+ rpc_layer="grpc")
+
+ # There are one CHIEF and three workers.
+ self.assertEqual(len(self._task_context), 2)
+ self.assertTrue(CHIEF in self._task_context)
+ self.assertTrue(WORKER in self._task_context)
+ self.assertEqual(len(self._task_context[CHIEF]), 1)
+ self.assertEqual(len(self._task_context[WORKER]), NUM_WORKERS)
+
+ # Check whether each task has the right master_target, num_workers, is_chief
+ # and distributed_mode.
+ self.assertEqual(self._task_context[CHIEF][0],
+ ("grpc://fake_chief", 4, True, True))
+ self.assertEqual(self._task_context[WORKER][0],
+ ("grpc://" + _bytes_to_str(self._workers[0].target),
+ NUM_WORKERS + 1, False, True))
+ self.assertEqual(self._task_context[WORKER][1],
+ ("grpc://" + _bytes_to_str(self._workers[1].target),
+ NUM_WORKERS + 1, False, True))
+ self.assertEqual(self._task_context[WORKER][2],
+ ("grpc://" + _bytes_to_str(self._workers[2].target),
+ NUM_WORKERS + 1, False, True))
+
+ def testInGraphContextWithEval(self):
+ # Adds a EVALUATOR job.
+ cluster_spec = copy.deepcopy(self._cluster_spec)
+ cluster_spec[EVALUATOR] = ["fake_evaluator"]
+
+ # Dumps the task contexts to the self._task_context dict.
+ distribute_coordinator.run_distribute_coordinator(
+ self._dump_task_context, cluster_spec=cluster_spec, between_graph=False)
+
+ # There are one "None" task and one EVALUATOR task.
+ self.assertEqual(len(self._task_context), 2)
+ self.assertTrue("None" in self._task_context)
+ self.assertTrue(EVALUATOR in self._task_context)
+ self.assertEqual(len(self._task_context["None"]), 1)
+ self.assertEqual(len(self._task_context[EVALUATOR]), 1)
+
+ # Check whether each task has the right master_target, num_workers, is_chief
+ # and distributed_mode.
+ self.assertEqual(self._task_context["None"][0],
+ (_bytes_to_str(self._workers[0].target), 3, True, True))
+ self.assertEqual(self._task_context[EVALUATOR][0],
+ ("fake_evaluator", 3, False, True))
+
+
+if __name__ == "__main__":
+ test.main()
diff --git a/tensorflow/python/eager/function.py b/tensorflow/python/eager/function.py
index 5e4f9e29da..99129c2537 100644
--- a/tensorflow/python/eager/function.py
+++ b/tensorflow/python/eager/function.py
@@ -93,10 +93,11 @@ def capture_value(tensor_map, value, dtype, name):
class CapturingGraph(ops.Graph):
"""Graph used when constructing eager functions."""
- def __init__(self, captures):
+ def __init__(self):
super(CapturingGraph, self).__init__()
self._building_function = True
- self.captures = captures
+ # Maps external tensor id -> internal tensor (e.g. input placeholder).
+ self.captures = {}
# Map from resource tensor name to last op (in program order) which uses
# this tensor. Used to enforce that execution order matches program order
# for resource tensors.
@@ -471,8 +472,7 @@ class GraphModeFunction(object):
def _construct_backprop_function(self):
"""Constructs the backprop function object for this function."""
filtered_outputs = [x for x in self._python_returns if x is not None]
- captures = {}
- backwards_graph = CapturingGraph(captures)
+ backwards_graph = CapturingGraph()
backwards_graph._graph_key = self._graph._graph_key # pylint: disable=protected-access
for collection in self._graph.collections:
backwards_graph.get_collection_ref(
@@ -491,6 +491,7 @@ class GraphModeFunction(object):
grad for grad in _flatten(in_gradients) if grad is not None)
output_shapes = tuple(grad.shape for grad in backward_outputs)
+ captures = backwards_graph.captures
ids = list(sorted(captures.keys()))
if ids:
extra_inputs, extra_placeholders = zip(*[captures[x] for x in ids])
@@ -660,8 +661,7 @@ def _deterministic_dict_values(kwds):
def _trace_and_define_function(name, func, compiled, args, kwds):
"""Defines and returns graph-mode version of func."""
graph_key = ops.get_default_graph()._graph_key # pylint: disable=protected-access
- captures = {}
- tmp_graph = CapturingGraph(captures)
+ tmp_graph = CapturingGraph()
# Inherit the graph key, since this is used for matching variables in
# optimizers.
tmp_graph._graph_key = graph_key # pylint: disable=protected-access
@@ -703,6 +703,7 @@ def _trace_and_define_function(name, func, compiled, args, kwds):
if x is not None
]
+ captures = tmp_graph.captures
ids = list(sorted(captures.keys()))
if ids:
extra_inputs, extra_placeholders = zip(* [captures[x] for x in ids])
diff --git a/tensorflow/python/eager/graph_callable.py b/tensorflow/python/eager/graph_callable.py
index 2c6f04d8ad..2dc5060984 100644
--- a/tensorflow/python/eager/graph_callable.py
+++ b/tensorflow/python/eager/graph_callable.py
@@ -280,8 +280,7 @@ def _graph_callable_internal(func, shape_and_dtypes):
# This graph will store both the initialization and the call version of the
# wrapped function. It will later be used by the backprop code to build the
# backprop graph, if necessary.
- captures = {}
- tmp_graph = function.CapturingGraph(captures)
+ tmp_graph = function.CapturingGraph()
# Inherit the graph key from the original graph to ensure optimizers don't
# misbehave.
tmp_graph._container = container # pylint: disable=protected-access
@@ -331,6 +330,7 @@ def _graph_callable_internal(func, shape_and_dtypes):
sorted_variables = sorted(variable_captures.variables.values(),
key=lambda x: x.name)
+ captures = tmp_graph.captures
ids = list(sorted(captures.keys()))
if ids:
extra_inputs, extra_placeholders = zip(*[captures[x] for x in ids])
diff --git a/tensorflow/python/eager/ops_test.py b/tensorflow/python/eager/ops_test.py
index fc76ede4c5..17a090d526 100644
--- a/tensorflow/python/eager/ops_test.py
+++ b/tensorflow/python/eager/ops_test.py
@@ -370,6 +370,10 @@ class OpsTest(test_util.TensorFlowTestCase):
with self.assertRaises(TypeError):
float(x)
+ def testRange(self):
+ x = constant_op.constant(2)
+ self.assertEqual([0, 1], list(range(x)))
+
def testFormatString(self):
x = constant_op.constant(3.1415)
self.assertEqual('3.14', '{:.2f}'.format(x))
diff --git a/tensorflow/python/eager/pywrap_tensor.cc b/tensorflow/python/eager/pywrap_tensor.cc
index cefd5b1206..15d2ccf9d2 100644
--- a/tensorflow/python/eager/pywrap_tensor.cc
+++ b/tensorflow/python/eager/pywrap_tensor.cc
@@ -154,6 +154,7 @@ TFE_TensorHandle* EagerCast(TFE_Context* ctx, TFE_TensorHandle* handle,
if (TF_GetCode(out_status) != TF_OK) RETURN_ERROR
TFE_OpSetAttrType(op, "SrcT", src_type_enum);
TFE_OpSetAttrType(op, "DstT", dst_type_enum);
+ TFE_OpSetAttrBool(op, "Truncate", false);
TFE_TensorHandle* output = nullptr;
int num_outputs = 1;
TFE_Execute(op, &output, &num_outputs, out_status);
diff --git a/tensorflow/python/eager/pywrap_tfe_src.cc b/tensorflow/python/eager/pywrap_tfe_src.cc
index 4d28e98961..0eabea321c 100644
--- a/tensorflow/python/eager/pywrap_tfe_src.cc
+++ b/tensorflow/python/eager/pywrap_tfe_src.cc
@@ -845,11 +845,9 @@ int64_t get_uid() {
PyObject* TFE_Py_UID() { return PyLong_FromLongLong(get_uid()); }
void TFE_DeleteContextCapsule(PyObject* context) {
- TF_Status* status = TF_NewStatus();
TFE_Context* ctx =
reinterpret_cast<TFE_Context*>(PyCapsule_GetPointer(context, nullptr));
- TFE_DeleteContext(ctx, status);
- TF_DeleteStatus(status);
+ TFE_DeleteContext(ctx);
}
static tensorflow::int64 MakeInt(PyObject* integer) {
diff --git a/tensorflow/python/estimator/BUILD b/tensorflow/python/estimator/BUILD
index fd46163050..817c8e6848 100644
--- a/tensorflow/python/estimator/BUILD
+++ b/tensorflow/python/estimator/BUILD
@@ -171,6 +171,7 @@ py_test(
name = "baseline_test",
size = "medium",
srcs = ["canned/baseline_test.py"],
+ shard_count = 4,
srcs_version = "PY2AND3",
tags = [
"no_pip",
@@ -207,6 +208,7 @@ py_test(
name = "boosted_trees_test",
size = "medium",
srcs = ["canned/boosted_trees_test.py"],
+ shard_count = 2,
srcs_version = "PY2AND3",
tags = [
"optonly",
@@ -676,6 +678,7 @@ py_test(
name = "keras_test",
size = "large",
srcs = ["keras_test.py"],
+ shard_count = 4,
srcs_version = "PY2AND3",
tags = [
"no_windows",
diff --git a/tensorflow/python/estimator/canned/boosted_trees.py b/tensorflow/python/estimator/canned/boosted_trees.py
index 3292e2724d..8b423f76de 100644
--- a/tensorflow/python/estimator/canned/boosted_trees.py
+++ b/tensorflow/python/estimator/canned/boosted_trees.py
@@ -46,7 +46,7 @@ from tensorflow.python.util.tf_export import estimator_export
# TODO(nponomareva): Reveal pruning params here.
_TreeHParams = collections.namedtuple('TreeHParams', [
'n_trees', 'max_depth', 'learning_rate', 'l1', 'l2', 'tree_complexity',
- 'min_node_weight', 'center_bias'
+ 'min_node_weight', 'center_bias', 'pruning_mode'
])
_HOLD_FOR_MULTI_CLASS_SUPPORT = object()
@@ -410,9 +410,20 @@ class _EnsembleGrower(object):
Args:
tree_ensemble: A TreeEnsemble variable.
tree_hparams: TODO. collections.namedtuple for hyper parameters.
+ Raises:
+ ValueError: when pruning mode is invalid or pruning is used and no tree
+ complexity is set.
"""
self._tree_ensemble = tree_ensemble
self._tree_hparams = tree_hparams
+ # pylint: disable=protected-access
+ self._pruning_mode_parsed = boosted_trees_ops.PruningMode.from_str(
+ tree_hparams.pruning_mode)
+
+ if (self._pruning_mode_parsed != boosted_trees_ops.PruningMode.NO_PRUNING
+ and tree_hparams.tree_complexity <= 0):
+ raise ValueError('For pruning, tree_complexity must be positive.')
+ # pylint: enable=protected-access
@abc.abstractmethod
def center_bias(self, center_bias_var, gradients, hessians):
@@ -500,7 +511,7 @@ class _EnsembleGrower(object):
right_node_contribs=right_node_contribs_list,
learning_rate=self._tree_hparams.learning_rate,
max_depth=self._tree_hparams.max_depth,
- pruning_mode=boosted_trees_ops.PruningMode.NO_PRUNING)
+ pruning_mode=self._pruning_mode_parsed)
return grow_op
@@ -675,6 +686,7 @@ def _bt_model_fn(
is_single_machine = (config.num_worker_replicas <= 1)
sorted_feature_columns = sorted(feature_columns, key=lambda tc: tc.name)
center_bias = tree_hparams.center_bias
+
if train_in_memory:
assert n_batches_per_layer == 1, (
'When train_in_memory is enabled, input_fn should return the entire '
@@ -925,7 +937,8 @@ class BoostedTreesClassifier(estimator.Estimator):
tree_complexity=0.,
min_node_weight=0.,
config=None,
- center_bias=False):
+ center_bias=False,
+ pruning_mode='none'):
"""Initializes a `BoostedTreesClassifier` instance.
Example:
@@ -999,7 +1012,11 @@ class BoostedTreesClassifier(estimator.Estimator):
regression problems, the first node will return the mean of the labels.
For binary classification problems, it will return a logit for a prior
probability of label 1.
-
+ pruning_mode: one of 'none', 'pre', 'post' to indicate no pruning, pre-
+ pruning (do not split a node if not enough gain is observed) and post
+ pruning (build the tree up to a max depth and then prune branches with
+ negative gain). For pre and post pruning, you MUST provide
+ tree_complexity >0.
Raises:
ValueError: when wrong arguments are given or unsupported functionalities
@@ -1012,9 +1029,9 @@ class BoostedTreesClassifier(estimator.Estimator):
n_classes, weight_column, label_vocabulary=label_vocabulary)
# HParams for the model.
- tree_hparams = _TreeHParams(n_trees, max_depth, learning_rate,
- l1_regularization, l2_regularization,
- tree_complexity, min_node_weight, center_bias)
+ tree_hparams = _TreeHParams(
+ n_trees, max_depth, learning_rate, l1_regularization, l2_regularization,
+ tree_complexity, min_node_weight, center_bias, pruning_mode)
def _model_fn(features, labels, mode, config):
return _bt_model_fn( # pylint: disable=protected-access
@@ -1058,7 +1075,8 @@ class BoostedTreesRegressor(estimator.Estimator):
tree_complexity=0.,
min_node_weight=0.,
config=None,
- center_bias=False):
+ center_bias=False,
+ pruning_mode='none'):
"""Initializes a `BoostedTreesRegressor` instance.
Example:
@@ -1125,6 +1143,11 @@ class BoostedTreesRegressor(estimator.Estimator):
regression problems, the first node will return the mean of the labels.
For binary classification problems, it will return a logit for a prior
probability of label 1.
+ pruning_mode: one of 'none', 'pre', 'post' to indicate no pruning, pre-
+ pruning (do not split a node if not enough gain is observed) and post
+ pruning (build the tree up to a max depth and then prune branches with
+ negative gain). For pre and post pruning, you MUST provide
+ tree_complexity >0.
Raises:
ValueError: when wrong arguments are given or unsupported functionalities
@@ -1136,9 +1159,9 @@ class BoostedTreesRegressor(estimator.Estimator):
head = _create_regression_head(label_dimension, weight_column)
# HParams for the model.
- tree_hparams = _TreeHParams(n_trees, max_depth, learning_rate,
- l1_regularization, l2_regularization,
- tree_complexity, min_node_weight, center_bias)
+ tree_hparams = _TreeHParams(
+ n_trees, max_depth, learning_rate, l1_regularization, l2_regularization,
+ tree_complexity, min_node_weight, center_bias, pruning_mode)
def _model_fn(features, labels, mode, config):
return _bt_model_fn( # pylint: disable=protected-access
diff --git a/tensorflow/python/estimator/canned/boosted_trees_test.py b/tensorflow/python/estimator/canned/boosted_trees_test.py
index f807641057..ec597e4686 100644
--- a/tensorflow/python/estimator/canned/boosted_trees_test.py
+++ b/tensorflow/python/estimator/canned/boosted_trees_test.py
@@ -1508,7 +1508,8 @@ class ModelFnTests(test_util.TensorFlowTestCase):
l2=0.01,
tree_complexity=0.,
min_node_weight=0.,
- center_bias=center_bias)
+ center_bias=center_bias,
+ pruning_mode='none')
estimator_spec = boosted_trees._bt_model_fn( # pylint:disable=protected-access
features=features,
diff --git a/tensorflow/python/estimator/estimator.py b/tensorflow/python/estimator/estimator.py
index 915ceeb98b..ffdcc7c80a 100644
--- a/tensorflow/python/estimator/estimator.py
+++ b/tensorflow/python/estimator/estimator.py
@@ -179,45 +179,17 @@ class Estimator(object):
"""
Estimator._assert_members_are_not_overridden(self)
- if config is None:
- self._config = run_config.RunConfig()
- logging.info('Using default config.')
- else:
- if not isinstance(config, run_config.RunConfig):
- raise ValueError(
- 'config must be an instance of RunConfig, but provided %s.' %
- config)
- self._config = config
+ config = maybe_overwrite_model_dir_and_session_config(config, model_dir)
+ self._config = config
# The distribute field contains an instance of DistributionStrategy.
- self._distribution = self._config.train_distribute
-
+ self._train_distribution = self._config.train_distribute
+ self._eval_distribution = self._config.eval_distribute
# Model directory.
- model_dir = compat_internal.path_to_str(model_dir)
- if (model_dir is not None) and (self._config.model_dir is not None):
- if model_dir != self._config.model_dir:
- # TODO(alanyee): remove this suppression after it is no longer needed
- # pylint: disable=g-doc-exception
- raise ValueError(
- "model_dir are set both in constructor and RunConfig, but with "
- "different values. In constructor: '{}', in RunConfig: "
- "'{}' ".format(model_dir, self._config.model_dir))
- # pylint: enable=g-doc-exception
-
- self._model_dir = model_dir or self._config.model_dir
- if self._model_dir is None:
- self._model_dir = tempfile.mkdtemp()
- logging.warning('Using temporary folder as model directory: %s',
- self._model_dir)
- if self._config.model_dir is None:
- self._config = self._config.replace(model_dir=self._model_dir)
+ self._model_dir = self._config.model_dir
+ self._session_config = self._config.session_config
logging.info('Using config: %s', str(vars(self._config)))
- if self._config.session_config is None:
- self._session_config = run_config.get_default_session_config()
- else:
- self._session_config = self._config.session_config
-
self._device_fn = (
self._config.device_fn or _get_replica_device_setter(self._config))
@@ -451,10 +423,9 @@ class Estimator(object):
'initialization to evaluate.'.format(self._model_dir))
checkpoint_path = latest_path
- with ops.Graph().as_default():
- (scaffold, update_op,
- eval_dict, all_hooks) = self._evaluate_build_graph(
- input_fn, hooks, checkpoint_path)
+ def _evaluate():
+ (scaffold, update_op, eval_dict, all_hooks) = (
+ self._evaluate_build_graph(input_fn, hooks, checkpoint_path))
return self._evaluate_run(
checkpoint_path=checkpoint_path,
scaffold=scaffold,
@@ -463,6 +434,15 @@ class Estimator(object):
all_hooks=all_hooks,
output_dir=self.eval_dir(name))
+ with ops.Graph().as_default():
+ # TODO(priyag): Support distributed eval on TPUs.
+ if (self._eval_distribution
+ and self._eval_distribution.__class__.__name__ != 'TPUStrategy'):
+ with self._eval_distribution.scope():
+ return _evaluate()
+ else:
+ return _evaluate()
+
def _convert_eval_steps_to_hooks(self, steps):
if steps is None:
return []
@@ -572,13 +552,14 @@ class Estimator(object):
return
allowed_overrides = set([
- '_call_input_fn', '_create_global_step',
+ '_call_input_fn', '_call_model_fn',
'_convert_train_steps_to_hooks', '_convert_eval_steps_to_hooks',
+ '_create_global_step', '_create_and_assert_global_step',
'_tf_api_names', '_tf_api_names_v1', '_estimator_api_names',
'_estimator_api_names_v1', '_estimator_api_constants',
'_estimator_api_constants_v1',
'_validate_features_in_predict_input',
- '_call_model_fn', '_add_meta_graph_for_mode'
+ '_add_meta_graph_for_mode'
])
estimator_members = set([m for m in Estimator.__dict__.keys()
if not m.startswith('__')])
@@ -905,9 +886,10 @@ class Estimator(object):
with tf_session.Session(config=self._session_config) as session:
- local_init_op = (
- estimator_spec.scaffold.local_init_op or
- monitored_session.Scaffold.default_local_init_op())
+ if estimator_spec.scaffold.local_init_op is not None:
+ local_init_op = estimator_spec.scaffold.local_init_op
+ else:
+ local_init_op = monitored_session.Scaffold.default_local_init_op()
# This saver will be used both for restoring variables now,
# and in saving out the metagraph below. This ensures that any
@@ -1000,10 +982,11 @@ class Estimator(object):
'QueueRunner. That means predict yields forever. '
'This is probably a mistake.')
- def _get_features_and_labels_from_input_fn(self, input_fn, mode):
+ def _get_features_and_labels_from_input_fn(self, input_fn, mode,
+ distribution=None):
"""Extracts the `features` and labels from return values of `input_fn`."""
- if self._distribution is not None and mode == model_fn_lib.ModeKeys.TRAIN:
- result = self._distribution.distribute_dataset(
+ if distribution is not None and mode == model_fn_lib.ModeKeys.TRAIN:
+ result = distribution.distribute_dataset(
lambda: self._call_input_fn(input_fn, mode))
else:
result = self._call_input_fn(input_fn, mode)
@@ -1137,7 +1120,7 @@ class Estimator(object):
return model_fn_results
def _train_model(self, input_fn, hooks, saving_listeners):
- if self._distribution:
+ if self._train_distribution:
return self._train_model_distributed(input_fn, hooks, saving_listeners)
else:
return self._train_model_default(input_fn, hooks, saving_listeners)
@@ -1159,13 +1142,19 @@ class Estimator(object):
with ops.Graph().as_default() as g, g.device(self._device_fn):
random_seed.set_random_seed(self._config.tf_random_seed)
global_step_tensor = self._create_and_assert_global_step(g)
- training_util._get_or_create_global_step_read() # pylint: disable=protected-access
+
+ # Skip creating a read variable if _create_and_assert_global_step
+ # returns None (e.g. tf.contrib.estimator.SavedModelEstimator).
+ if global_step_tensor is not None:
+ training_util._get_or_create_global_step_read(g) # pylint: disable=protected-access
+
features, labels, input_hooks = (
self._get_features_and_labels_from_input_fn(
input_fn, model_fn_lib.ModeKeys.TRAIN))
worker_hooks.extend(input_hooks)
estimator_spec = self._call_model_fn(
features, labels, model_fn_lib.ModeKeys.TRAIN, self.config)
+ global_step_tensor = training_util.get_global_step(g)
return self._train_with_estimator_spec(estimator_spec, worker_hooks,
hooks, global_step_tensor,
saving_listeners)
@@ -1183,22 +1172,23 @@ class Estimator(object):
Returns:
Loss from training
"""
- self._distribution.configure(self._session_config)
+ self._train_distribution.configure(self._session_config)
# TODO(sourabhbajaj): Remove this hack once we migrate the other strategies
# to use the new API
- is_tpu_strategy = self._distribution.__class__.__name__ == 'TPUStrategy'
+ is_tpu_strategy = (
+ self._train_distribution.__class__.__name__ == 'TPUStrategy')
worker_hooks = []
with ops.Graph().as_default() as g:
- with self._distribution.scope():
+ with self._train_distribution.scope():
random_seed.set_random_seed(self._config.tf_random_seed)
if is_tpu_strategy:
# Create the iterator for run_on_dataset function
# TODO(sourabhbajaj): refactor this out to call a function on the
# strategy
- dataset = self._distribution.distribute_dataset(
+ dataset = self._train_distribution.distribute_dataset(
lambda: self._call_input_fn(input_fn, # pylint: disable=g-long-lambda
model_fn_lib.ModeKeys.TRAIN))
iterator = dataset.make_initializable_iterator()
@@ -1208,14 +1198,15 @@ class Estimator(object):
global_step_tensor = self._create_and_assert_global_step(g)
# we want to add to the global collection in the main thread not the
# tower threads.
- ops.add_to_collection(training_util.GLOBAL_STEP_READ_KEY,
- self._distribution.read_var(global_step_tensor))
+ ops.add_to_collection(
+ training_util.GLOBAL_STEP_READ_KEY,
+ self._train_distribution.read_var(global_step_tensor))
# Create a step_fn from the train_op of grouped_estimator_spec
def step_fn(ctx, inputs):
"""A single step that is passed to run_on_dataset."""
features, labels = inputs
- estimator_spec = self._distribution.call_for_each_tower(
+ estimator_spec = self._train_distribution.call_for_each_tower(
self._call_model_fn,
features,
labels,
@@ -1231,103 +1222,34 @@ class Estimator(object):
# work correctly. Currently hardcoded at 2
initial_training_loss = constant_op.constant(1e7)
distributed_train_op, tpu_result, ctx = \
- self._distribution._run_steps_on_dataset( # pylint: disable=protected-access
+ self._train_distribution._run_steps_on_dataset( # pylint: disable=protected-access
step_fn, iterator, iterations=2,
initial_loop_values=initial_training_loss)
grouped_estimator_spec = ctx.non_tensor_outputs['estimator_spec']
else:
features, labels, input_hooks = (
self._get_features_and_labels_from_input_fn(
- input_fn, model_fn_lib.ModeKeys.TRAIN))
+ input_fn, model_fn_lib.ModeKeys.TRAIN,
+ self._train_distribution))
worker_hooks.extend(input_hooks)
global_step_tensor = self._create_and_assert_global_step(g)
# we want to add to the global collection in the main thread not the
# tower threads.
- ops.add_to_collection(training_util.GLOBAL_STEP_READ_KEY,
- self._distribution.read_var(global_step_tensor))
- grouped_estimator_spec = self._distribution.call_for_each_tower(
+ ops.add_to_collection(
+ training_util.GLOBAL_STEP_READ_KEY,
+ self._train_distribution.read_var(global_step_tensor))
+ grouped_estimator_spec = self._train_distribution.call_for_each_tower(
self._call_model_fn,
features,
labels, # although this will be None it seems
model_fn_lib.ModeKeys.TRAIN,
self.config)
- # TODO(anjalisridhar): Figure out how to resolve the following scaffold
- # parameters: init_feed_dict, init_fn.
- scaffold_list = self._distribution.unwrap(
- grouped_estimator_spec.scaffold)
- init_feed_dict = [
- s.init_feed_dict
- for s in scaffold_list
- if s.init_feed_dict is not None
- ]
- if init_feed_dict:
- init_feed_dict = self._distribution.group(init_feed_dict)
- else:
- init_feed_dict = None
-
- init_fn = [s.init_fn for s in scaffold_list if s.init_fn is not None]
- if init_fn:
- init_fn = self._distribution.group(init_fn)
- else:
- init_fn = None
-
- init_op = [s.init_op for s in scaffold_list if s.init_op is not None]
- if init_op:
- init_op = self._distribution.group(init_op)
- else:
- init_op = None
-
- def _unwrap_and_concat(value):
- value = nest.flatten(self._distribution.unwrap(value))
- if len(value) != 1:
- return array_ops.concat(value)
- return value[0]
-
- ready_op = self._distribution.call_for_each_tower(
- create_per_tower_ready_op, grouped_estimator_spec.scaffold)
- if ready_op is not None:
- ready_op = _unwrap_and_concat(ready_op)
- else:
- ready_op = None
-
- ready_for_local_init_op = self._distribution.call_for_each_tower(
- create_per_tower_ready_for_local_init_op,
- grouped_estimator_spec.scaffold)
- if ready_for_local_init_op is not None:
- ready_for_local_init_op = _unwrap_and_concat(ready_for_local_init_op)
- else:
- ready_for_local_init_op = None
-
- local_init_op = [
- s.local_init_op
- for s in scaffold_list
- if s.local_init_op is not None
- ]
- if local_init_op:
- local_init_op = self._distribution.group(local_init_op)
- else:
- local_init_op = None
-
- summary_op = [
- s.summary_op for s in scaffold_list if s.summary_op is not None
- ]
- if summary_op:
- summary_op = self._distribution.group(summary_op)
- else:
- summary_op = None
-
- scaffold = monitored_session.Scaffold(
- init_op=init_op,
- ready_op=ready_op,
- ready_for_local_init_op=ready_for_local_init_op,
- local_init_op=local_init_op,
- summary_op=summary_op,
- init_feed_dict=init_feed_dict,
- init_fn=init_fn)
+ scaffold = _combine_distributed_scaffold(
+ grouped_estimator_spec.scaffold, self._train_distribution)
def get_hooks_from_the_first_device(per_device_hooks):
- hooks_list = self._distribution.unwrap(per_device_hooks)
+ hooks_list = self._train_distribution.unwrap(per_device_hooks)
assert hooks_list
return hooks_list[0]
@@ -1340,24 +1262,25 @@ class Estimator(object):
# handle per device variables correctly in reduce and can output
# the loss scaler.
if is_tpu_strategy:
- loss = self._distribution.unwrap(
- self._distribution.reduce(distribute_lib.get_loss_reduction(),
- tpu_result)[0])[0]
+ loss = self._train_distribution.unwrap(
+ self._train_distribution.reduce(
+ distribute_lib.get_loss_reduction(), tpu_result)[0])[0]
worker_hooks.append(
estimator_util.StrategyInitFinalizeHook(
- self._distribution.get_initialization_ops,
- self._distribution.get_finalize_ops))
+ self._train_distribution.get_initialization_ops,
+ self._train_distribution.get_finalize_ops))
else:
- loss = self._distribution.unwrap(
- self._distribution.reduce(distribute_lib.get_loss_reduction(),
- grouped_estimator_spec.loss,
- destinations='/device:CPU:0'))[0]
+ loss = self._train_distribution.unwrap(
+ self._train_distribution.reduce(
+ distribute_lib.get_loss_reduction(),
+ grouped_estimator_spec.loss,
+ destinations='/device:CPU:0'))[0]
distributed_train_op = grouped_estimator_spec.train_op
estimator_spec = model_fn_lib.EstimatorSpec(
mode=grouped_estimator_spec.mode,
loss=loss,
- train_op=self._distribution.group(distributed_train_op),
+ train_op=self._train_distribution.group(distributed_train_op),
training_hooks=training_hooks,
training_chief_hooks=training_chief_hooks,
scaffold=scaffold)
@@ -1452,27 +1375,31 @@ class Estimator(object):
def _evaluate_build_graph(self, input_fn, hooks=None, checkpoint_path=None):
"""Builds the graph and related hooks to run evaluation."""
random_seed.set_random_seed(self._config.tf_random_seed)
- global_step_tensor = self._create_and_assert_global_step(
- ops.get_default_graph())
+ self._create_and_assert_global_step(ops.get_default_graph())
features, labels, input_hooks = (
- self._get_features_and_labels_from_input_fn(input_fn,
- model_fn_lib.ModeKeys.EVAL))
- estimator_spec = self._call_model_fn(
- features, labels, model_fn_lib.ModeKeys.EVAL, self.config)
+ self._get_features_and_labels_from_input_fn(
+ input_fn, model_fn_lib.ModeKeys.EVAL, self._eval_distribution))
+ if self._eval_distribution:
+ (loss_metric, scaffold, evaluation_hooks, eval_metric_ops) = (
+ self._call_model_fn_eval_distributed(features, labels, self.config))
+ else:
+ (loss_metric, scaffold, evaluation_hooks, eval_metric_ops) = (
+ self._call_model_fn_eval(features, labels, self.config))
+
+ global_step_tensor = training_util.get_global_step(ops.get_default_graph())
# Call to warm_start has to be after model_fn is called.
self._maybe_warm_start(checkpoint_path)
- if model_fn_lib.LOSS_METRIC_KEY in estimator_spec.eval_metric_ops:
+ if model_fn_lib.LOSS_METRIC_KEY in eval_metric_ops:
raise ValueError(
'Metric with name "%s" is not allowed, because Estimator ' %
(model_fn_lib.LOSS_METRIC_KEY) +
'already defines a default metric with the same name.')
- estimator_spec.eval_metric_ops[
- model_fn_lib.LOSS_METRIC_KEY] = metrics_lib.mean(estimator_spec.loss)
+ eval_metric_ops[model_fn_lib.LOSS_METRIC_KEY] = loss_metric
- update_op, eval_dict = _extract_metric_update_ops(
- estimator_spec.eval_metric_ops)
+ update_op, eval_dict = _extract_metric_update_ops(eval_metric_ops,
+ self._eval_distribution)
if ops.GraphKeys.GLOBAL_STEP in eval_dict:
raise ValueError(
@@ -1482,9 +1409,42 @@ class Estimator(object):
all_hooks = list(input_hooks)
all_hooks.extend(hooks)
- all_hooks.extend(list(estimator_spec.evaluation_hooks or []))
+ all_hooks.extend(list(evaluation_hooks or []))
+ # New local variables have been added, so update the estimator spec's
+ # local init op if it was defined.
+ if scaffold and scaffold.local_init_op:
+ # Ensure that eval step has been created before updating local init op.
+ evaluation._get_or_create_eval_step() # pylint: disable=protected-access
+
+ scaffold = monitored_session.Scaffold(
+ local_init_op=control_flow_ops.group(
+ scaffold.local_init_op,
+ monitored_session.Scaffold.default_local_init_op()),
+ copy_from_scaffold=scaffold
+ )
+
+ return scaffold, update_op, eval_dict, all_hooks
- return estimator_spec.scaffold, update_op, eval_dict, all_hooks
+ def _call_model_fn_eval(self, features, labels, config):
+ estimator_spec = self._call_model_fn(
+ features, labels, model_fn_lib.ModeKeys.EVAL, config)
+ loss_metric = metrics_lib.mean(estimator_spec.loss)
+ return (loss_metric, estimator_spec.scaffold,
+ estimator_spec.evaluation_hooks, estimator_spec.eval_metric_ops)
+
+ def _call_model_fn_eval_distributed(self, features, labels, config):
+ """Call model_fn in distribution mode and handle return values."""
+ grouped_estimator_spec = self._eval_distribution.call_for_each_tower(
+ self._call_model_fn, features, labels,
+ model_fn_lib.ModeKeys.EVAL, config)
+ scaffold = _combine_distributed_scaffold(
+ grouped_estimator_spec.scaffold, self._eval_distribution)
+ evaluation_hooks = self._eval_distribution.unwrap(
+ grouped_estimator_spec.evaluation_hooks)[0]
+ loss_metric = self._eval_distribution.call_for_each_tower(
+ metrics_lib.mean, grouped_estimator_spec.loss)
+ return (loss_metric, scaffold,
+ evaluation_hooks, grouped_estimator_spec.eval_metric_ops)
def _evaluate_run(self, checkpoint_path, scaffold, update_op, eval_dict,
all_hooks, output_dir):
@@ -1520,6 +1480,49 @@ class Estimator(object):
warm_starting_util.warm_start(*self._warm_start_settings)
+def maybe_overwrite_model_dir_and_session_config(config, model_dir):
+ """Overwrite estimator config by `model_dir` and `session_config` if needed.
+
+ Args:
+ config: Original estimator config.
+ model_dir: Estimator model checkpoint directory.
+
+ Returns:
+ Overwritten estimator config.
+
+ Raises:
+ ValueError: Model directory inconsistent between `model_dir` and `config`.
+ """
+
+ if config is None:
+ config = run_config.RunConfig()
+ logging.info('Using default config.')
+ if not isinstance(config, run_config.RunConfig):
+ raise ValueError(
+ 'config must be an instance of `RunConfig`, but provided %s.' % config)
+
+ if config.session_config is None:
+ session_config = run_config.get_default_session_config()
+ config = run_config.RunConfig.replace(config, session_config=session_config)
+
+ model_dir = compat_internal.path_to_str(model_dir)
+ if model_dir is not None:
+ if (getattr(config, 'model_dir', None) is not None and
+ config.model_dir != model_dir):
+ raise ValueError(
+ "`model_dir` are set both in constructor and `RunConfig`, but with "
+ "different values. In constructor: '{}', in `RunConfig`: "
+ "'{}' ".format(model_dir, config.model_dir))
+ if model_dir:
+ config = run_config.RunConfig.replace(config, model_dir=model_dir)
+ if getattr(config, 'model_dir', None) is None:
+ model_dir = tempfile.mkdtemp()
+ logging.warning('Using temporary folder as model directory: %s', model_dir)
+ config = run_config.RunConfig.replace(config, model_dir=model_dir)
+
+ return config
+
+
def create_per_tower_ready_op(scaffold):
"""Create a Scaffold.ready_op inside a tower."""
if scaffold.ready_op:
@@ -1549,6 +1552,83 @@ def create_per_tower_ready_for_local_init_op(scaffold):
default_ready_for_local_init_op)
+def _combine_distributed_scaffold(grouped_scaffold, distribution):
+ """Combines scaffold(s) returned from `distribution.call_for_each_tower`."""
+
+ # TODO(anjalisridhar): Figure out how to resolve the following scaffold
+ # parameters: init_feed_dict, init_fn.
+ scaffold_list = distribution.unwrap(grouped_scaffold)
+ init_feed_dict = [
+ s.init_feed_dict
+ for s in scaffold_list
+ if s.init_feed_dict is not None
+ ]
+ if init_feed_dict:
+ init_feed_dict = distribution.group(init_feed_dict)
+ else:
+ init_feed_dict = None
+
+ init_fn = [s.init_fn for s in scaffold_list if s.init_fn is not None]
+ if init_fn:
+ init_fn = distribution.group(init_fn)
+ else:
+ init_fn = None
+
+ init_op = [s.init_op for s in scaffold_list if s.init_op is not None]
+ if init_op:
+ init_op = distribution.group(init_op)
+ else:
+ init_op = None
+
+ def _unwrap_and_concat(value):
+ value = nest.flatten(distribution.unwrap(value))
+ if len(value) != 1:
+ return array_ops.concat(value)
+ return value[0]
+
+ ready_op = distribution.call_for_each_tower(
+ create_per_tower_ready_op, grouped_scaffold)
+ if ready_op is not None:
+ ready_op = _unwrap_and_concat(ready_op)
+ else:
+ ready_op = None
+
+ ready_for_local_init_op = distribution.call_for_each_tower(
+ create_per_tower_ready_for_local_init_op, grouped_scaffold)
+ if ready_for_local_init_op is not None:
+ ready_for_local_init_op = _unwrap_and_concat(ready_for_local_init_op)
+ else:
+ ready_for_local_init_op = None
+
+ local_init_op = [
+ s.local_init_op
+ for s in scaffold_list
+ if s.local_init_op is not None
+ ]
+ if local_init_op:
+ local_init_op = distribution.group(local_init_op)
+ else:
+ local_init_op = None
+
+ summary_op = [
+ s.summary_op for s in scaffold_list if s.summary_op is not None
+ ]
+ if summary_op:
+ summary_op = distribution.group(summary_op)
+ else:
+ summary_op = None
+
+ scaffold = monitored_session.Scaffold(
+ init_op=init_op,
+ ready_op=ready_op,
+ ready_for_local_init_op=ready_for_local_init_op,
+ local_init_op=local_init_op,
+ summary_op=summary_op,
+ init_feed_dict=init_feed_dict,
+ init_fn=init_fn)
+ return scaffold
+
+
def _check_checkpoint_available(model_dir):
latest_path = saver.latest_checkpoint(model_dir)
if not latest_path:
@@ -1633,14 +1713,18 @@ def _load_global_step_from_checkpoint_dir(checkpoint_dir):
return 0
-def _extract_metric_update_ops(eval_dict):
+def _extract_metric_update_ops(eval_dict, distribution=None):
"""Separate update operations from metric value operations."""
update_ops = []
value_ops = {}
# Sort metrics lexicographically so graph is identical every time.
for name, metric_ops in sorted(six.iteritems(eval_dict)):
value_ops[name] = metric_ops[0]
- update_ops.append(metric_ops[1])
+ if distribution:
+ update_op = distribution.group(metric_ops[1])
+ else:
+ update_op = metric_ops[1]
+ update_ops.append(update_op)
if update_ops:
update_op = control_flow_ops.group(*update_ops)
@@ -1915,6 +1999,19 @@ class WarmStartSettings(
)
+def _get_saved_model_ckpt(saved_model_dir):
+ """Return path to variables checkpoint in a SavedModel directory."""
+ if not gfile.Exists(
+ os.path.join(compat.as_bytes(saved_model_dir),
+ compat.as_bytes('variables/variables.index'))):
+ raise ValueError('Directory provided has an invalid SavedModel format: %s'
+ % saved_model_dir)
+ return os.path.join(
+ compat.as_bytes(saved_model_dir),
+ compat.as_bytes('{}/{}'.format(constants.VARIABLES_DIRECTORY,
+ constants.VARIABLES_FILENAME)))
+
+
def _get_default_warm_start_settings(warm_start_from):
"""Returns default WarmStartSettings.
@@ -1938,10 +2035,8 @@ def _get_default_warm_start_settings(warm_start_from):
if gfile.Exists(os.path.join(compat.as_bytes(warm_start_from),
compat.as_bytes('variables/variables.index'))):
logging.info('Warm-starting from a SavedModel')
- return WarmStartSettings(ckpt_to_initialize_from=os.path.join(
- compat.as_bytes(warm_start_from),
- compat.as_bytes('{}/{}'.format(constants.VARIABLES_DIRECTORY,
- constants.VARIABLES_FILENAME))))
+ return WarmStartSettings(
+ ckpt_to_initialize_from=_get_saved_model_ckpt(warm_start_from))
return WarmStartSettings(ckpt_to_initialize_from=warm_start_from)
elif isinstance(warm_start_from, WarmStartSettings):
return warm_start_from
diff --git a/tensorflow/python/estimator/estimator_test.py b/tensorflow/python/estimator/estimator_test.py
index 8bc410ba0b..68fc5bcadf 100644
--- a/tensorflow/python/estimator/estimator_test.py
+++ b/tensorflow/python/estimator/estimator_test.py
@@ -175,7 +175,7 @@ class EstimatorInheritanceConstraintTest(test.TestCase):
class EstimatorConstructorTest(test.TestCase):
def test_config_must_be_a_run_config(self):
- with self.assertRaisesRegexp(ValueError, 'an instance of RunConfig'):
+ with self.assertRaisesRegexp(ValueError, 'an instance of `RunConfig`'):
estimator.Estimator(model_fn=None, config='NotARunConfig')
def test_model_fn_must_be_provided(self):
@@ -228,6 +228,15 @@ class EstimatorConstructorTest(test.TestCase):
self.assertEqual(_TMP_DIR, est.config.model_dir)
self.assertEqual(_TMP_DIR, est.model_dir)
+ def test_empty_model_dir(self):
+ def model_fn(features, labels):
+ _, _ = features, labels
+
+ with test.mock.patch.object(tempfile, 'mkdtemp', return_value=_TMP_DIR):
+ est = estimator.Estimator(model_fn=model_fn, model_dir='')
+ self.assertEqual(_TMP_DIR, est.config.model_dir)
+ self.assertEqual(_TMP_DIR, est.model_dir)
+
def test_model_dir_in_run_config(self):
class FakeConfig(run_config.RunConfig):
@@ -272,7 +281,7 @@ class EstimatorConstructorTest(test.TestCase):
with self.assertRaisesRegexp(
ValueError,
- 'model_dir are set both in constructor and RunConfig, but '
+ '`model_dir` are set both in constructor and `RunConfig`, but '
'with different values'):
estimator.Estimator(
model_fn=model_fn, config=FakeConfig(), model_dir=_ANOTHER_TMP_DIR)
diff --git a/tensorflow/python/estimator/keras.py b/tensorflow/python/estimator/keras.py
index 70517ae278..079560c495 100644
--- a/tensorflow/python/estimator/keras.py
+++ b/tensorflow/python/estimator/keras.py
@@ -21,14 +21,11 @@ from __future__ import print_function
import os
import re
-import tempfile
from tensorflow.python.client import session
from tensorflow.python.estimator import estimator as estimator_lib
from tensorflow.python.estimator import export as export_lib
from tensorflow.python.estimator import model_fn as model_fn_lib
-from tensorflow.python.estimator import run_config as run_config_lib
-from tensorflow.python.estimator.run_config import RunConfig
from tensorflow.python.framework import ops
from tensorflow.python.framework import random_seed
from tensorflow.python.framework import sparse_tensor as sparse_tensor_lib
@@ -473,43 +470,6 @@ def _save_first_checkpoint(keras_model, custom_objects, config):
return latest_path
-def _maybe_overwrite_model_dir_and_session_config(config, model_dir):
- """Overwrite estimator config by `model_dir` and `session_config` if needed.
-
- Args:
- config: Original estimator config.
- model_dir: Estimator model checkpoint directory.
-
- Returns:
- Overwritten estimator config.
-
- Raises:
- ValueError: Model directory inconsistent between `model_dir` and `config`.
- """
-
- default_session_config = run_config_lib.get_default_session_config()
- if isinstance(config, dict):
- config = RunConfig(**config)
- elif config is None:
- config = RunConfig(session_config=default_session_config)
- if config.session_config is None:
- config = RunConfig.replace(config, session_config=default_session_config)
-
- if model_dir is not None:
- if (getattr(config, 'model_dir', None) is not None and
- config.model_dir != model_dir):
- raise ValueError(
- "`model_dir` are set both in constructor and `RunConfig`, but with "
- "different values. In constructor: '{}', in `RunConfig`: "
- "'{}' ".format(model_dir, config.model_dir))
- config = RunConfig.replace(config, model_dir=model_dir)
- elif getattr(config, 'model_dir', None) is None:
- model_dir = tempfile.mkdtemp()
- config = RunConfig.replace(config, model_dir=model_dir)
-
- return config
-
-
def model_to_estimator(keras_model=None,
keras_model_path=None,
custom_objects=None,
@@ -527,9 +487,9 @@ def model_to_estimator(keras_model=None,
format, which can be generated with the `save()` method of a Keras model.
This argument is mutually exclusive with `keras_model`.
custom_objects: Dictionary for custom objects.
- model_dir: Directory to save Estimator model parameters, graph, summary
+ model_dir: Directory to save `Estimator` model parameters, graph, summary
files for TensorBoard, etc.
- config: Configuration object.
+ config: `RunConfig` to config `Estimator`.
Returns:
An Estimator from given keras model.
@@ -566,7 +526,8 @@ def model_to_estimator(keras_model=None,
'Please compile the model with `model.compile()` '
'before calling `model_to_estimator()`.')
- config = _maybe_overwrite_model_dir_and_session_config(config, model_dir)
+ config = estimator_lib.maybe_overwrite_model_dir_and_session_config(config,
+ model_dir)
keras_model_fn = _create_keras_model_fn(keras_model, custom_objects)
if _any_weight_initialized(keras_model):
diff --git a/tensorflow/python/estimator/keras_test.py b/tensorflow/python/estimator/keras_test.py
index cf4ec7f4da..332e385726 100644
--- a/tensorflow/python/estimator/keras_test.py
+++ b/tensorflow/python/estimator/keras_test.py
@@ -275,11 +275,7 @@ class TestKerasEstimator(test_util.TensorFlowTestCase):
with self.test_session():
est_keras = keras_lib.model_to_estimator(
keras_model=keras_model,
- # Also use dict config argument to get test coverage for that line.
- config={
- 'tf_random_seed': _RANDOM_SEED,
- 'model_dir': self._base_dir,
- })
+ config=self._config)
before_eval_results = est_keras.evaluate(
input_fn=eval_input_fn, steps=1)
est_keras.train(input_fn=train_input_fn, steps=_TRAIN_SIZE / 16)
diff --git a/tensorflow/python/estimator/model_fn.py b/tensorflow/python/estimator/model_fn.py
index a9fd8f8e1a..9db9ccd01d 100644
--- a/tensorflow/python/estimator/model_fn.py
+++ b/tensorflow/python/estimator/model_fn.py
@@ -380,15 +380,12 @@ def _maybe_add_default_serving_output(export_outputs):
return export_outputs
-class _TPUEstimatorSpec(collections.namedtuple('TPUEstimatorSpec', [
- 'mode',
- 'predictions',
- 'loss',
- 'train_op',
- 'eval_metrics',
- 'export_outputs',
- 'scaffold_fn',
- 'host_call'])):
+class _TPUEstimatorSpec(
+ collections.namedtuple('TPUEstimatorSpec', [
+ 'mode', 'predictions', 'loss', 'train_op', 'eval_metrics',
+ 'export_outputs', 'scaffold_fn', 'host_call', 'training_hooks',
+ 'evaluation_hooks', 'prediction_hooks'
+ ])):
"""Ops and objects returned from a `model_fn` and passed to `TPUEstimator`.
This is a simplified implementation of `tf.contrib.tpu.EstimatorSpec`. See
@@ -404,17 +401,24 @@ class _TPUEstimatorSpec(collections.namedtuple('TPUEstimatorSpec', [
eval_metrics=None,
export_outputs=None,
scaffold_fn=None,
- host_call=None):
+ host_call=None,
+ training_hooks=None,
+ evaluation_hooks=None,
+ prediction_hooks=None):
"""Creates a `_TPUEstimatorSpec` instance."""
- return super(_TPUEstimatorSpec, cls).__new__(cls,
- mode=mode,
- predictions=predictions,
- loss=loss,
- train_op=train_op,
- eval_metrics=eval_metrics,
- export_outputs=export_outputs,
- scaffold_fn=scaffold_fn,
- host_call=host_call)
+ return super(_TPUEstimatorSpec, cls).__new__(
+ cls,
+ mode=mode,
+ predictions=predictions,
+ loss=loss,
+ train_op=train_op,
+ eval_metrics=eval_metrics,
+ export_outputs=export_outputs,
+ scaffold_fn=scaffold_fn,
+ host_call=host_call,
+ training_hooks=training_hooks,
+ evaluation_hooks=evaluation_hooks,
+ prediction_hooks=prediction_hooks)
def as_estimator_spec(self):
"""Creates an equivalent `EstimatorSpec` used by CPU train/eval."""
@@ -423,12 +427,16 @@ class _TPUEstimatorSpec(collections.namedtuple('TPUEstimatorSpec', [
else:
metric_fn, tensors = self.eval_metrics
eval_metric_ops = metric_fn(**tensors)
- return EstimatorSpec(mode=self.mode,
- predictions=self.predictions,
- loss=self.loss,
- train_op=self.train_op,
- eval_metric_ops=eval_metric_ops,
- export_outputs=self.export_outputs)
+ return EstimatorSpec(
+ mode=self.mode,
+ predictions=self.predictions,
+ loss=self.loss,
+ train_op=self.train_op,
+ eval_metric_ops=eval_metric_ops,
+ export_outputs=self.export_outputs,
+ training_hooks=self.training_hooks,
+ evaluation_hooks=self.evaluation_hooks,
+ prediction_hooks=self.prediction_hooks)
def _check_is_tensor_or_operation(x, name):
diff --git a/tensorflow/python/estimator/run_config.py b/tensorflow/python/estimator/run_config.py
index 6c1de166a4..220c3e58ca 100644
--- a/tensorflow/python/estimator/run_config.py
+++ b/tensorflow/python/estimator/run_config.py
@@ -49,7 +49,8 @@ _DEFAULT_REPLACEABLE_LIST = [
'log_step_count_steps',
'train_distribute',
'device_fn',
- 'protocol'
+ 'protocol',
+ 'eval_distribute',
]
_SAVE_CKPT_ERR = (
@@ -329,7 +330,8 @@ class RunConfig(object):
log_step_count_steps=100,
train_distribute=None,
device_fn=None,
- protocol=None):
+ protocol=None,
+ eval_distribute=None):
"""Constructs a RunConfig.
All distributed training related properties `cluster_spec`, `is_chief`,
@@ -463,6 +465,10 @@ class RunConfig(object):
with round-robin strategy.
protocol: An optional argument which specifies the protocol used when
starting server. None means default to grpc.
+ eval_distribute: An optional instance of
+ `tf.contrib.distribute.DistributionStrategy`. If specified,
+ then Estimator will distribute the user's model during evaluation,
+ according to the policy specified by that strategy.
Raises:
ValueError: If both `save_checkpoints_steps` and `save_checkpoints_secs`
@@ -501,7 +507,8 @@ class RunConfig(object):
log_step_count_steps=log_step_count_steps,
train_distribute=train_distribute,
device_fn=device_fn,
- protocol=protocol)
+ protocol=protocol,
+ eval_distribute=eval_distribute)
self._init_distributed_setting_from_environment_var(tf_config)
@@ -770,11 +777,17 @@ class RunConfig(object):
@property
def train_distribute(self):
- """Returns the optional `tf.contrib.distribute.DistributionStrategy` object.
+ """Optional `tf.contrib.distribute.DistributionStrategy` for training.
"""
return self._train_distribute
@property
+ def eval_distribute(self):
+ """Optional `tf.contrib.distribute.DistributionStrategy` for evaluation.
+ """
+ return self._eval_distribute
+
+ @property
def protocol(self):
"""Returns the optional protocol value."""
return self._protocol
@@ -796,6 +809,7 @@ class RunConfig(object):
- `train_distribute`,
- `device_fn`,
- `protocol`.
+ - `eval_distribute`,
In addition, either `save_checkpoints_steps` or `save_checkpoints_secs`
can be set (should not be both).
diff --git a/tensorflow/python/framework/error_interpolation.py b/tensorflow/python/framework/error_interpolation.py
index a79073b748..7719d03019 100644
--- a/tensorflow/python/framework/error_interpolation.py
+++ b/tensorflow/python/framework/error_interpolation.py
@@ -87,6 +87,53 @@ def _parse_message(message):
return seps, tags
+def _compute_device_summary_from_list(device_assignment_list, prefix=""):
+ """Return a summary of an op's device function stack.
+
+ Args:
+ device_assignment_list: The op._device_assignments list.
+ prefix: An optional string prefix used before each line of the multi-
+ line string returned by this function.
+
+ Returns:
+ A multi-line string similar to:
+ Device assignments active during op creation:
+ with tf.device(/cpu:0): <test_1.py:27>
+ with tf.device(some_func<foo.py, 123>): <test_2.py:38>
+ The first line will have no padding to its left by default. Subsequent
+ lines will have two spaces of left-padding. Use the prefix argument
+ to increase indentation.
+ """
+ if not device_assignment_list:
+ message = "No device assignments were active during op creation."
+ return prefix + message
+
+ str_list = []
+ str_list.append("%sDevice assignments active during op creation:" % prefix)
+
+ for traceable_obj in device_assignment_list:
+ location_summary = "<{file}:{line}>".format(file=traceable_obj.filename,
+ line=traceable_obj.lineno)
+ subs = {
+ "prefix": prefix,
+ "indent": " ",
+ "dev_name": traceable_obj.obj,
+ "loc": location_summary,
+ }
+ str_list.append(
+ "{prefix}{indent}with tf.device({dev_name}): {loc}".format(**subs))
+
+ return "\n".join(str_list)
+
+
+def _compute_device_assignment_summary_from_op(op, prefix=""):
+ if not op:
+ return ""
+ # pylint: disable=protected-access
+ return _compute_device_summary_from_list(op._device_assignments, prefix)
+ # pylint: enable=protected-access
+
+
def _compute_colocation_summary_from_dict(colocation_dict, prefix=""):
"""Return a summary of an op's colocation stack.
@@ -203,6 +250,7 @@ def _compute_field_dict(op):
"file": default_value,
"line": default_value,
"colocations": default_value,
+ "devices": default_value,
}
frame = _get_defining_frame_from_op(op)
if frame:
@@ -211,6 +259,9 @@ def _compute_field_dict(op):
colocation_summary = _compute_colocation_summary_from_op(op)
if colocation_summary:
field_dict["colocations"] = colocation_summary
+ device_summary = _compute_device_assignment_summary_from_op(op)
+ if device_summary:
+ field_dict["devices"] = device_summary
return field_dict
@@ -233,6 +284,8 @@ def interpolate(error_message, graph):
node_name_to_substitution_dict = {}
for name in [t.name for t in tags]:
+ if name in node_name_to_substitution_dict:
+ continue
try:
op = graph.get_operation_by_name(name)
except KeyError:
diff --git a/tensorflow/python/framework/error_interpolation_test.py b/tensorflow/python/framework/error_interpolation_test.py
index 1e5cb73854..fbf182879b 100644
--- a/tensorflow/python/framework/error_interpolation_test.py
+++ b/tensorflow/python/framework/error_interpolation_test.py
@@ -57,13 +57,32 @@ def _modify_op_stack_with_filenames(op, num_user_frames, user_filename,
op._traceback = stack
-def assert_node_in_colocation_summary(test_obj, colocation_summary_string,
- name, filename="", lineno=""):
- lineno = str(lineno)
- name_phrase = "colocate_with(%s)" % name
- for term in [name_phrase, filename, lineno]:
- test_obj.assertIn(term, colocation_summary_string)
- test_obj.assertNotIn("loc:@", colocation_summary_string)
+class ComputeDeviceSummaryFromOpTest(test.TestCase):
+
+ def testCorrectFormatWithActiveDeviceAssignments(self):
+ assignments = []
+ assignments.append(
+ traceable_stack.TraceableObject("/cpu:0",
+ filename="hope.py",
+ lineno=24))
+ assignments.append(
+ traceable_stack.TraceableObject("/gpu:2",
+ filename="please.py",
+ lineno=42))
+
+ summary = error_interpolation._compute_device_summary_from_list(
+ assignments, prefix=" ")
+
+ self.assertIn("tf.device(/cpu:0)", summary)
+ self.assertIn("<hope.py:24>", summary)
+ self.assertIn("tf.device(/gpu:2)", summary)
+ self.assertIn("<please.py:42>", summary)
+
+ def testCorrectFormatWhenNoColocationsWereActive(self):
+ device_assignment_list = []
+ summary = error_interpolation._compute_device_summary_from_list(
+ device_assignment_list, prefix=" ")
+ self.assertIn("No device assignments", summary)
class ComputeColocationSummaryFromOpTest(test.TestCase):
@@ -81,15 +100,10 @@ class ComputeColocationSummaryFromOpTest(test.TestCase):
}
summary = error_interpolation._compute_colocation_summary_from_dict(
colocation_dict, prefix=" ")
- assert_node_in_colocation_summary(self,
- summary,
- name="test_node_1",
- filename="test_1.py",
- lineno=27)
- assert_node_in_colocation_summary(self, summary,
- name="test_node_2",
- filename="test_2.py",
- lineno=38)
+ self.assertIn("colocate_with(test_node_1)", summary)
+ self.assertIn("<test_1.py:27>", summary)
+ self.assertIn("colocate_with(test_node_2)", summary)
+ self.assertIn("<test_2.py:38>", summary)
def testCorrectFormatWhenNoColocationsWereActive(self):
colocation_dict = {}
@@ -98,9 +112,10 @@ class ComputeColocationSummaryFromOpTest(test.TestCase):
self.assertIn("No node-device colocations", summary)
-class InterpolateTest(test.TestCase):
+class InterpolateFilenamesAndLineNumbersTest(test.TestCase):
def setUp(self):
+ ops.reset_default_graph()
# Add nodes to the graph for retrieval by name later.
constant_op.constant(1, name="One")
constant_op.constant(2, name="Two")
@@ -177,9 +192,57 @@ class InterpolateTest(test.TestCase):
self.assertRegexpMatches(interpolated_string, expected_regex)
+class InterpolateDeviceSummaryTest(test.TestCase):
+
+ def _fancy_device_function(self, unused_op):
+ return "/cpu:*"
+
+ def setUp(self):
+ ops.reset_default_graph()
+ self.zero = constant_op.constant([0.0], name="zero")
+ with ops.device("/cpu"):
+ self.one = constant_op.constant([1.0], name="one")
+ with ops.device("/cpu:0"):
+ self.two = constant_op.constant([2.0], name="two")
+ with ops.device(self._fancy_device_function):
+ self.three = constant_op.constant(3.0, name="three")
+
+ self.graph = self.three.graph
+
+ def testNodeZeroHasNoDeviceSummaryInfo(self):
+ message = "^^node:zero:${devices}^^"
+ result = error_interpolation.interpolate(message, self.graph)
+ self.assertIn("No device assignments were active", result)
+
+ def testNodeOneHasExactlyOneInterpolatedDevice(self):
+ message = "^^node:one:${devices}^^"
+ result = error_interpolation.interpolate(message, self.graph)
+ num_devices = result.count("tf.device")
+ self.assertEqual(1, num_devices)
+ self.assertIn("tf.device(/cpu)", result)
+
+ def testNodeTwoHasTwoInterpolatedDevice(self):
+ message = "^^node:two:${devices}^^"
+ result = error_interpolation.interpolate(message, self.graph)
+ num_devices = result.count("tf.device")
+ self.assertEqual(2, num_devices)
+ self.assertIn("tf.device(/cpu)", result)
+ self.assertIn("tf.device(/cpu:0)", result)
+
+ def testNodeThreeHasFancyFunctionDisplayNameForInterpolatedDevice(self):
+ message = "^^node:three:${devices}^^"
+ result = error_interpolation.interpolate(message, self.graph)
+ num_devices = result.count("tf.device")
+ self.assertEqual(1, num_devices)
+ name_re = r"_fancy_device_function<.*error_interpolation_test.py, [0-9]+>"
+ expected_re = r"with tf.device\(.*%s\)" % name_re
+ self.assertRegexpMatches(result, expected_re)
+
+
class InterpolateColocationSummaryTest(test.TestCase):
def setUp(self):
+ ops.reset_default_graph()
# Add nodes to the graph for retrieval by name later.
node_one = constant_op.constant(1, name="One")
node_two = constant_op.constant(2, name="Two")
@@ -203,12 +266,12 @@ class InterpolateColocationSummaryTest(test.TestCase):
def testNodeThreeHasColocationInterpolation(self):
message = "^^node:Three_with_one:${colocations}^^"
result = error_interpolation.interpolate(message, self.graph)
- assert_node_in_colocation_summary(self, result, name="One")
+ self.assertIn("colocate_with(One)", result)
def testNodeFourHasColocationInterpolationForNodeThreeOnly(self):
message = "^^node:Four_with_three:${colocations}^^"
result = error_interpolation.interpolate(message, self.graph)
- assert_node_in_colocation_summary(self, result, name="Three_with_one")
+ self.assertIn("colocate_with(Three_with_one)", result)
self.assertNotIn(
"One", result,
"Node One should not appear in Four_with_three's summary:\n%s"
@@ -217,8 +280,8 @@ class InterpolateColocationSummaryTest(test.TestCase):
def testNodeFiveHasColocationInterpolationForNodeOneAndTwo(self):
message = "^^node:Five_with_one_with_two:${colocations}^^"
result = error_interpolation.interpolate(message, self.graph)
- assert_node_in_colocation_summary(self, result, name="One")
- assert_node_in_colocation_summary(self, result, name="Two")
+ self.assertIn("colocate_with(One)", result)
+ self.assertIn("colocate_with(Two)", result)
def testColocationInterpolationForNodeLackingColocation(self):
message = "^^node:One:${colocations}^^"
diff --git a/tensorflow/python/framework/fast_tensor_util.pyx b/tensorflow/python/framework/fast_tensor_util.pyx
index 17d112a1ec..2e3e15f53a 100644
--- a/tensorflow/python/framework/fast_tensor_util.pyx
+++ b/tensorflow/python/framework/fast_tensor_util.pyx
@@ -6,6 +6,13 @@ cimport numpy as np
from tensorflow.python.util import compat
+def AppendBFloat16ArrayToTensorProto(
+ tensor_proto, np.ndarray[np.uint16_t, ndim=1] nparray):
+ cdef long i, n
+ n = nparray.size
+ for i in range(n):
+ tensor_proto.half_val.append(nparray[i])
+
def AppendFloat16ArrayToTensorProto(
# For numpy, npy_half is a typedef for npy_uint16,
diff --git a/tensorflow/python/framework/function.py b/tensorflow/python/framework/function.py
index 6525607fae..c76743d2c6 100644
--- a/tensorflow/python/framework/function.py
+++ b/tensorflow/python/framework/function.py
@@ -38,8 +38,8 @@ from tensorflow.python.ops import cond_v2_impl
from tensorflow.python.ops import resource_variable_ops
from tensorflow.python.ops import variable_scope as vs
from tensorflow.python.util import compat
+from tensorflow.python.util import function_utils
from tensorflow.python.util import tf_contextlib
-from tensorflow.python.util import tf_decorator
from tensorflow.python.util import tf_inspect
# This is to avoid a circular dependency with cond_v2_impl.
@@ -255,9 +255,12 @@ class _DefinedFunction(object):
# Constructed only when C API is enabled, lazily
self._c_func = None
self._sub_functions = dict() # Constructed with _definition or _c_func
- device_stack = ops.get_default_graph()._device_function_stack # pylint: disable=protected-access
+ # pylint: disable=protected-access
+ device_funcs = ops.get_default_graph()._device_functions_outer_to_inner
+ # pylint: enable=protected-access
+
# Get the innermost device if possbile.
- self._caller_device = device_stack[-1] if device_stack else None
+ self._caller_device = device_funcs[-1] if device_funcs else None
# Cached OpDef for this function. When C API is enabled, this is
# the only part of FunctionDef that we cache in Python. When C API
@@ -354,7 +357,7 @@ class _DefinedFunction(object):
if self._func_name:
base_func_name = self._func_name
else:
- base_func_name = _get_func_name(self._func)
+ base_func_name = function_utils.get_func_name(self._func)
if self._grad_func:
base_func_name += ("_%s" % self._grad_func.name)
kwargs_attr = _parse_kwargs_as_attrs(base_func_name, **self._extra_kwargs)
@@ -841,7 +844,7 @@ def func_graph_from_py_func(func, arg_names, arg_types, name=None,
ValueError: if func returns None.
"""
if not name:
- name = _get_func_name(func)
+ name = function_utils.get_func_name(func)
func_graph = _FuncGraph(name, capture_by_value)
with func_graph.as_default(), ops.device(device):
@@ -1139,19 +1142,6 @@ def _parse_kwargs_as_attrs(func_name, **kwargs):
return attrs
-def _get_func_name(func):
- _, func = tf_decorator.unwrap(func)
- if callable(func):
- if tf_inspect.isfunction(func):
- return func.__name__
- elif tf_inspect.ismethod(func):
- return "%s.%s" % (func.__self__.__name__, func.__name__)
- else: # Probably a class instance with __call__
- return type(func)
- else:
- raise ValueError("Argument must be callable")
-
-
def get_extra_vars():
"""Returns the captured variables by the function.
diff --git a/tensorflow/python/framework/importer.py b/tensorflow/python/framework/importer.py
index 699d2b70d1..687bfebd43 100644
--- a/tensorflow/python/framework/importer.py
+++ b/tensorflow/python/framework/importer.py
@@ -205,7 +205,7 @@ def _PopulateTFImportGraphDefOptions(options, prefix, input_map,
for input_src, input_dst in input_map.items():
input_src = compat.as_str(input_src)
if input_src.startswith('^'):
- src_name = compat.as_bytes(input_src[1:])
+ src_name = compat.as_str(input_src[1:])
dst_op = input_dst._as_tf_output().oper # pylint: disable=protected-access
c_api.TF_ImportGraphDefOptionsRemapControlDependency(
options, src_name, dst_op)
diff --git a/tensorflow/python/framework/importer_test.py b/tensorflow/python/framework/importer_test.py
index c5a54470d2..7182c28666 100644
--- a/tensorflow/python/framework/importer_test.py
+++ b/tensorflow/python/framework/importer_test.py
@@ -30,6 +30,7 @@ from tensorflow.python.framework import dtypes
from tensorflow.python.framework import function
from tensorflow.python.framework import importer
from tensorflow.python.framework import ops
+from tensorflow.python.framework import tensor_shape
from tensorflow.python.framework import test_ops # pylint: disable=unused-import
from tensorflow.python.framework import versions
from tensorflow.python.ops import array_ops
@@ -419,6 +420,46 @@ class ImportGraphDefTest(test.TestCase):
with self.test_session() as sess:
self.assertEqual(sess.run(imported_r), 10)
+ def testImportWhileLoopInCond(self):
+ # Produce GraphDef containing while loop.
+ graph = ops.Graph()
+ with graph.as_default():
+ r = control_flow_ops.while_loop(lambda i: i < 10, lambda i: i + 1, [0])
+ graph_def = graph.as_graph_def()
+
+ # Import the GraphDef inside a cond and make sure it runs.
+ with ops.Graph().as_default():
+
+ def ImportFn():
+ return importer.import_graph_def(graph_def, return_elements=[r.name])[0]
+
+ pred = array_ops.placeholder(dtypes.bool)
+ out = control_flow_ops.cond(pred, ImportFn,
+ lambda: constant_op.constant(1))
+ with self.test_session() as sess:
+ self.assertEqual(sess.run(out, {pred: True}), 10)
+ self.assertEqual(sess.run(out, {pred: False}), 1)
+
+ def testImportWhileLoopInWhileLoop(self):
+ self.skipTest("b/111757448")
+ # Produce GraphDef containing while loop.
+ graph = ops.Graph()
+ with graph.as_default():
+ r = control_flow_ops.while_loop(lambda i: i < 10, lambda i: i + 1, [0])
+ graph_def = graph.as_graph_def()
+
+ # Import the GraphDef inside another loop and make sure it runs.
+ with ops.Graph().as_default():
+
+ def ImportFn(_):
+ return importer.import_graph_def(graph_def, return_elements=[r.name])[0]
+
+ out = control_flow_ops.while_loop(
+ lambda i: i < 2, ImportFn, [0],
+ shape_invariants=[tensor_shape.TensorShape(None)])
+ with self.test_session() as sess:
+ self.assertEqual(sess.run(out), 10)
+
def testTypeMismatchInGraphDef(self):
# TODO(skyewm): improve error message
error_msg = ("Input 0 of node import/B was passed int32 from import/A:0 "
diff --git a/tensorflow/python/framework/meta_graph.py b/tensorflow/python/framework/meta_graph.py
index 923e76fc9c..33631282bd 100644
--- a/tensorflow/python/framework/meta_graph.py
+++ b/tensorflow/python/framework/meta_graph.py
@@ -696,6 +696,67 @@ def import_scoped_meta_graph(meta_graph_or_file,
Raises:
ValueError: If the graph_def contains unbound inputs.
"""
+ return import_scoped_meta_graph_with_return_elements(
+ meta_graph_or_file, clear_devices, graph, import_scope, input_map,
+ unbound_inputs_col_name, restore_collections_predicate)[0]
+
+
+def import_scoped_meta_graph_with_return_elements(
+ meta_graph_or_file,
+ clear_devices=False,
+ graph=None,
+ import_scope=None,
+ input_map=None,
+ unbound_inputs_col_name="unbound_inputs",
+ restore_collections_predicate=(lambda key: True),
+ return_elements=None):
+ """Imports graph from `MetaGraphDef` and returns vars and return elements.
+
+ This function takes a `MetaGraphDef` protocol buffer as input. If
+ the argument is a file containing a `MetaGraphDef` protocol buffer ,
+ it constructs a protocol buffer from the file content. The function
+ then adds all the nodes from the `graph_def` field to the
+ current graph, recreates the desired collections, and returns a dictionary of
+ all the Variables imported into the name scope.
+
+ In combination with `export_scoped_meta_graph()`, this function can be used to
+
+ * Serialize a graph along with other Python objects such as `QueueRunner`,
+ `Variable` into a `MetaGraphDef`.
+
+ * Restart training from a saved graph and checkpoints.
+
+ * Run inference from a saved graph and checkpoints.
+
+ Args:
+ meta_graph_or_file: `MetaGraphDef` protocol buffer or filename (including
+ the path) containing a `MetaGraphDef`.
+ clear_devices: Boolean which controls whether to clear device information
+ from graph_def. Default false.
+ graph: The `Graph` to import into. If `None`, use the default graph.
+ import_scope: Optional `string`. Name scope into which to import the
+ subgraph. If `None`, the graph is imported to the root name scope.
+ input_map: A dictionary mapping input names (as strings) in `graph_def` to
+ `Tensor` objects. The values of the named input tensors in the imported
+ graph will be re-mapped to the respective `Tensor` values.
+ unbound_inputs_col_name: Collection name for looking up unbound inputs.
+ restore_collections_predicate: a predicate on collection names. A collection
+ named c (i.e whose key is c) will be restored iff
+ 1) `restore_collections_predicate(c)` is True, and
+ 2) `c != unbound_inputs_col_name`.
+ return_elements: A list of strings containing operation names in the
+ `MetaGraphDef` that will be returned as `Operation` objects; and/or
+ tensor names in `MetaGraphDef` that will be returned as `Tensor` objects.
+
+ Returns:
+ A tuple of (
+ dictionary of all the `Variables` imported into the name scope,
+ list of `Operation` or `Tensor` objects from the `return_elements` list).
+
+ Raises:
+ ValueError: If the graph_def contains unbound inputs.
+
+ """
if context.executing_eagerly():
raise ValueError("Exporting/importing meta graphs is not supported when "
"eager execution is enabled.")
@@ -737,11 +798,12 @@ def import_scoped_meta_graph(meta_graph_or_file,
scope_to_prepend_to_names = graph.unique_name(
import_scope or "", mark_as_used=False)
- importer.import_graph_def(
+ imported_return_elements = importer.import_graph_def(
input_graph_def,
name=(import_scope or scope_to_prepend_to_names),
input_map=input_map,
- producer_op_list=producer_op_list)
+ producer_op_list=producer_op_list,
+ return_elements=return_elements)
# Restores all the other collections.
variable_objects = {}
@@ -806,7 +868,7 @@ def import_scoped_meta_graph(meta_graph_or_file,
for v in variables:
var_list[ops.strip_name_scope(v.name, scope_to_prepend_to_names)] = v
- return var_list
+ return var_list, imported_return_elements
def export_scoped_meta_graph(filename=None,
diff --git a/tensorflow/python/framework/ops.py b/tensorflow/python/framework/ops.py
index 6a5c44e4d9..c25e29b0f4 100644
--- a/tensorflow/python/framework/ops.py
+++ b/tensorflow/python/framework/ops.py
@@ -50,14 +50,15 @@ from tensorflow.python.framework import registry
from tensorflow.python.framework import tensor_shape
from tensorflow.python.framework import traceable_stack
from tensorflow.python.framework import versions
-from tensorflow.python.util import tf_stack
from tensorflow.python.ops import control_flow_util
from tensorflow.python.platform import app
from tensorflow.python.platform import tf_logging as logging
from tensorflow.python.util import compat
from tensorflow.python.util import decorator_utils
+from tensorflow.python.util import function_utils
from tensorflow.python.util import lock_util
from tensorflow.python.util import tf_contextlib
+from tensorflow.python.util import tf_stack
from tensorflow.python.util.deprecation import deprecated_args
from tensorflow.python.util.tf_export import tf_export
@@ -73,6 +74,31 @@ def tensor_id(tensor):
return tensor._id # pylint: disable=protected-access
+class _UserDeviceSpec(object):
+ """Store user-specified device and provide computation of merged device."""
+
+ def __init__(self, device_name_or_function):
+ self._device_name_or_function = device_name_or_function
+
+ self.display_name = str(self._device_name_or_function)
+ if callable(self._device_name_or_function):
+ dev_func = self._device_name_or_function
+ func_name = function_utils.get_func_name(dev_func)
+ func_code = function_utils.get_func_code(dev_func)
+ if func_code:
+ fname = func_code.co_filename
+ lineno = func_code.co_firstlineno
+ else:
+ fname = "unknown"
+ lineno = -1
+ self.display_name = "%s<%s, %d>" % (func_name, fname, lineno)
+
+ self.function = self._device_name_or_function
+ if not (self._device_name_or_function is None or
+ callable(self._device_name_or_function)):
+ self.function = pydev.merge_device(self._device_name_or_function)
+
+
class _NullContextmanager(object):
def __enter__(self):
@@ -709,7 +735,7 @@ class _EagerTensorBase(Tensor):
raise ValueError("Resource handles are not convertible to numpy.")
return self._cpu_nograd()._numpy() # pylint: disable=protected-access
- # __int__ and __float__ may copy the tensor to CPU and
+ # __int__, __float__ and __index__ may copy the tensor to CPU and
# only work for scalars; values are cast as per numpy.
def __int__(self):
return int(self.numpy())
@@ -717,6 +743,9 @@ class _EagerTensorBase(Tensor):
def __float__(self):
return float(self.numpy())
+ def __index__(self):
+ return int(self.numpy())
+
def __array__(self, dtype=None):
return np.array(self.numpy(), dtype=dtype)
@@ -1716,7 +1745,12 @@ class Operation(object):
self._id_value = self._graph._next_id()
self._original_op = original_op
self._traceback = tf_stack.extract_stack()
- # List of traceable_stack.TraceableObjects for colocation context managers.
+
+ # List of _UserDevSpecs holding code location of device context manager
+ # invocations and the users original argument to them.
+ self._device_code_locations = None
+ # Dict mapping op name to file and line information for op colocation
+ # context managers.
self._colocation_code_locations = None
self._control_flow_context = self.graph._get_control_flow_context()
# pylint: enable=protected-access
@@ -1858,6 +1892,37 @@ class Operation(object):
return c_api.TF_OperationDevice(self._c_op)
@property
+ def _device_assignments(self):
+ """Code locations for device context managers active at op creation.
+
+ This property will return a list of traceable_stack.TraceableObject
+ instances where .obj is a string representing the assigned device
+ (or information about the function that would be applied to this op
+ to compute the desired device) and the filename and lineno members
+ record the location of the relevant device context manager.
+
+ For example, suppose file_a contained these lines:
+
+ file_a.py:
+ 15: with tf.device('/gpu:0'):
+ 16: node_b = tf.constant(4, name='NODE_B')
+
+ Then a TraceableObject t_obj representing the device context manager
+ would have these member values:
+
+ t_obj.obj -> '/gpu:0'
+ t_obj.filename = 'file_a.py'
+ t_obj.lineno = 15
+
+ and node_b.op._device_assignments would return the list [t_obj].
+
+ Returns:
+ [str: traceable_stack.TraceableObject, ...] as per this method's
+ description, above.
+ """
+ return self._device_code_locations or []
+
+ @property
def _colocation_dict(self):
"""Code locations for colocation context managers active at op creation.
@@ -1878,11 +1943,10 @@ class Operation(object):
would have these member values:
t_obj.obj -> None
- t_obj.name = 'NODE_A'
t_obj.filename = 'file_a.py'
t_obj.lineno = 15
- and node_b.op._colocation_code_locations would return the dictionary
+ and node_b.op._colocation_dict would return the dictionary
{ 'NODE_A': t_obj }
@@ -2732,7 +2796,7 @@ class Graph(object):
# Functions that will be applied to choose a device if none is specified.
# After switch_to_thread_local(), self._thread_local._device_function_stack
# is used instead.
- self._graph_device_function_stack = []
+ self._graph_device_function_stack = traceable_stack.TraceableStack()
# Default original_op applied to new ops.
self._default_original_op = None
# Current control flow context. It could be either CondContext or
@@ -3776,8 +3840,8 @@ class Graph(object):
Nothing.
"""
old_original_op = self._default_original_op
+ self._default_original_op = op
try:
- self._default_original_op = op
yield
finally:
self._default_original_op = old_original_op
@@ -3894,15 +3958,15 @@ class Graph(object):
# op name regex, which constrains the initial character.
if not _VALID_OP_NAME_REGEX.match(name):
raise ValueError("'%s' is not a valid scope name" % name)
+ old_stack = self._name_stack
+ if not name: # Both for name=None and name="" we re-set to empty scope.
+ new_stack = None
+ elif name[-1] == "/":
+ new_stack = _name_from_scope_name(name)
+ else:
+ new_stack = self.unique_name(name)
+ self._name_stack = new_stack
try:
- old_stack = self._name_stack
- if not name: # Both for name=None and name="" we re-set to empty scope.
- new_stack = None
- elif name[-1] == "/":
- new_stack = _name_from_scope_name(name)
- else:
- new_stack = self.unique_name(name)
- self._name_stack = new_stack
yield "" if new_stack is None else new_stack + "/"
finally:
self._name_stack = old_stack
@@ -3983,8 +4047,8 @@ class Graph(object):
ignore_existing=False):
with self.colocate_with(op, ignore_existing):
if gradient_uid is not None and self._control_flow_context is not None:
+ self._control_flow_context.EnterGradientColocation(op, gradient_uid)
try:
- self._control_flow_context.EnterGradientColocation(op, gradient_uid)
yield
finally:
self._control_flow_context.ExitGradientColocation(op, gradient_uid)
@@ -4026,7 +4090,6 @@ class Graph(object):
Yields:
A context manager that specifies the op with which to colocate
newly created ops.
-
"""
if op is None and not ignore_existing:
raise ValueError("Trying to reset colocation (op is None) but "
@@ -4044,7 +4107,7 @@ class Graph(object):
# In the future, a caller may specify that device_functions win
# over colocation, in which case we can add support.
device_fn_tmp = self._device_function_stack
- self._device_function_stack = []
+ self._device_function_stack = traceable_stack.TraceableStack()
if ignore_existing:
current_stack = self._colocation_stack
@@ -4068,6 +4131,13 @@ class Graph(object):
if ignore_existing:
self._colocation_stack = current_stack
+ def _add_device_to_stack(self, device_name_or_function, offset=0):
+ """Add device to stack manually, separate from a context manager."""
+ total_offset = 1 + offset
+ spec = _UserDeviceSpec(device_name_or_function)
+ self._device_function_stack.push_obj(spec, offset=total_offset)
+ return spec
+
@tf_contextlib.contextmanager
def device(self, device_name_or_function):
# pylint: disable=line-too-long
@@ -4125,31 +4195,26 @@ class Graph(object):
Yields:
A context manager that specifies the default device to use for newly
created ops.
-
"""
- # pylint: enable=line-too-long
- if (device_name_or_function is not None and
- not callable(device_name_or_function)):
- device_function = pydev.merge_device(device_name_or_function)
- else:
- device_function = device_name_or_function
-
+ self._add_device_to_stack(device_name_or_function, offset=2)
try:
- self._device_function_stack.append(device_function)
yield
finally:
- self._device_function_stack.pop()
+ self._device_function_stack.pop_obj()
def _apply_device_functions(self, op):
"""Applies the current device function stack to the given operation."""
- # Apply any device functions in reverse order, so that the most recently
+ # Apply any device functions in LIFO order, so that the most recently
# pushed function has the first chance to apply a device to the op.
# We apply here because the result can depend on the Operation's
# signature, which is computed in the Operation constructor.
- for device_function in reversed(self._device_function_stack):
- if device_function is None:
+ # pylint: disable=protected-access
+ for device_spec in self._device_function_stack.peek_objs():
+ if device_spec.function is None:
break
- op._set_device(device_function(op)) # pylint: disable=protected-access
+ op._set_device(device_spec.function(op))
+ op._device_code_locations = self._snapshot_device_function_stack_metadata()
+ # pylint: enable=protected-access
# pylint: disable=g-doc-return-or-yield
@tf_contextlib.contextmanager
@@ -4198,8 +4263,8 @@ class Graph(object):
yields the container name.
"""
original_container = self._container
+ self._container = container_name
try:
- self._container = container_name
yield self._container
finally:
self._container = original_container
@@ -4673,17 +4738,45 @@ class Graph(object):
if self._stack_state_is_thread_local:
# This may be called from a thread where device_function_stack doesn't yet
# exist.
+ # pylint: disable=protected-access
if not hasattr(self._thread_local, "_device_function_stack"):
- self._thread_local._device_function_stack = (
- self._graph_device_function_stack[:])
+ stack_copy_for_this_thread = self._graph_device_function_stack.copy()
+ self._thread_local._device_function_stack = stack_copy_for_this_thread
return self._thread_local._device_function_stack
+ # pylint: enable=protected-access
else:
return self._graph_device_function_stack
+ @property
+ def _device_functions_outer_to_inner(self):
+ user_device_specs = self._device_function_stack.peek_objs()
+ device_functions = [spec.function for spec in user_device_specs]
+ device_functions_outer_to_inner = list(reversed(device_functions))
+ return device_functions_outer_to_inner
+
+ def _snapshot_device_function_stack_metadata(self):
+ """Return device function stack as a list of TraceableObjects.
+
+ Returns:
+ [traceable_stack.TraceableObject, ...] where each TraceableObject's .obj
+ member is a displayable name for the user's argument to Graph.device, and
+ the filename and lineno members point to the code location where
+ Graph.device was called directly or indirectly by the user.
+ """
+ traceable_objects = self._device_function_stack.peek_traceable_objs()
+ snapshot = []
+ for obj in traceable_objects:
+ obj_copy = obj.copy_metadata()
+ obj_copy.obj = obj.obj.display_name
+ snapshot.append(obj_copy)
+ return snapshot
+
@_device_function_stack.setter
def _device_function_stack(self, device_function_stack):
if self._stack_state_is_thread_local:
+ # pylint: disable=protected-access
self._thread_local._device_function_stack = device_function_stack
+ # pylint: enable=protected-access
else:
self._graph_device_function_stack = device_function_stack
@@ -4693,12 +4786,12 @@ class Graph(object):
if self._stack_state_is_thread_local:
# This may be called from a thread where colocation_stack doesn't yet
# exist.
+ # pylint: disable=protected-access
if not hasattr(self._thread_local, "_colocation_stack"):
stack_copy_for_this_thread = self._graph_colocation_stack.copy()
- # pylint: disable=protected-access
self._thread_local._colocation_stack = stack_copy_for_this_thread
- # pylint: enable=protected-access
return self._thread_local._colocation_stack
+ # pylint: enable=protected-access
else:
return self._graph_colocation_stack
@@ -4710,7 +4803,9 @@ class Graph(object):
@_colocation_stack.setter
def _colocation_stack(self, colocation_stack):
if self._stack_state_is_thread_local:
+ # pylint: disable=protected-access
self._thread_local._colocation_stack = colocation_stack
+ # pylint: enable=protected-access
else:
self._graph_colocation_stack = colocation_stack
@@ -4879,8 +4974,8 @@ class _DefaultStack(threading.local):
@tf_contextlib.contextmanager
def get_controller(self, default):
"""A context manager for manipulating a default stack."""
+ self.stack.append(default)
try:
- self.stack.append(default)
yield default
finally:
# stack may be empty if reset() was called
@@ -5068,13 +5163,15 @@ class _DefaultGraphStack(_DefaultStack): # pylint: disable=protected-access
@tf_contextlib.contextmanager
def get_controller(self, default):
+ context.context().context_switches.push(
+ default.building_function, default.as_default)
try:
- context.context().context_switches.push(
- default.building_function, default.as_default)
with super(_DefaultGraphStack, self).get_controller(
default) as g, context.graph_mode():
yield g
finally:
+ # If an exception is raised here it may be hiding a related exception in
+ # the try-block (just above).
context.context().context_switches.pop()
@@ -5110,6 +5207,9 @@ def init_scope():
`init_scope` will simply install a fresh graph as the default one.
(3) The gradient tape is paused while the scope is active.
+
+ Raises:
+ RuntimeError: if graph state is incompatible with this initialization.
"""
# pylint: enable=g-doc-return-or-yield,line-too-long
@@ -5122,10 +5222,10 @@ def init_scope():
# the name scope of the current context.
default_graph = get_default_graph()
scope = default_graph.get_name_scope()
- if scope and scope[-1] != '/':
+ if scope and scope[-1] != "/":
# Names that end with trailing slashes are treated by `name_scope` as
# absolute.
- scope = scope + '/'
+ scope = scope + "/"
inner_device_stack = default_graph._device_function_stack # pylint: disable=protected-access
outer_context = None
@@ -5170,6 +5270,8 @@ def init_scope():
outer_graph._device_function_stack = inner_device_stack # pylint: disable=protected-access
yield
finally:
+ # If an exception is raised here it may be hiding a related exception in
+ # try-block (just above).
if outer_graph is not None:
outer_graph._device_function_stack = outer_device_stack # pylint: disable=protected-access
diff --git a/tensorflow/python/framework/ops_test.py b/tensorflow/python/framework/ops_test.py
index f848b69782..48328a7f58 100644
--- a/tensorflow/python/framework/ops_test.py
+++ b/tensorflow/python/framework/ops_test.py
@@ -19,6 +19,7 @@ from __future__ import division
from __future__ import print_function
import gc
+import os
import threading
import weakref
@@ -2542,6 +2543,56 @@ class StatisticsTest(test_util.TensorFlowTestCase):
self.assertEqual(3, flops_total.value)
+class DeviceStackTest(test_util.TensorFlowTestCase):
+
+ def testBasicDeviceAssignmentMetadata(self):
+
+ def device_func(unused_op):
+ return "/cpu:*"
+
+ const_zero = constant_op.constant([0.0], name="zero")
+ with ops.device("/cpu"):
+ const_one = constant_op.constant([1.0], name="one")
+ with ops.device("/cpu:0"):
+ const_two = constant_op.constant([2.0], name="two")
+ with ops.device(device_func):
+ const_three = constant_op.constant(3.0, name="three")
+
+ self.assertEqual(0, len(const_zero.op._device_assignments))
+
+ one_list = const_one.op._device_assignments
+ self.assertEqual(1, len(one_list))
+ self.assertEqual("/cpu", one_list[0].obj)
+ self.assertEqual("ops_test.py", os.path.basename(one_list[0].filename))
+
+ two_list = const_two.op._device_assignments
+ self.assertEqual(2, len(two_list))
+ devices = [t.obj for t in two_list]
+ self.assertEqual(set(["/cpu", "/cpu:0"]), set(devices))
+
+ three_list = const_three.op._device_assignments
+ self.assertEqual(1, len(three_list))
+ func_description = three_list[0].obj
+ expected_regex = r"device_func<.*ops_test.py, [0-9]+"
+ self.assertRegexpMatches(func_description, expected_regex)
+
+ def testDeviceAssignmentMetadataForGraphDeviceAndTfDeviceFunctions(self):
+
+ with ops.device("/cpu"):
+ const_one = constant_op.constant([1.0], name="one")
+ with ops.get_default_graph().device("/cpu"):
+ const_two = constant_op.constant([2.0], name="two")
+
+ one_metadata = const_one.op._device_assignments[0]
+ two_metadata = const_two.op._device_assignments[0]
+
+ # Verify both types of device assignment return the right stack info.
+ self.assertRegexpMatches("ops_test.py",
+ os.path.basename(one_metadata.filename))
+ self.assertEqual(one_metadata.filename, two_metadata.filename)
+ self.assertEqual(one_metadata.lineno + 2, two_metadata.lineno)
+
+
class ColocationGroupTest(test_util.TensorFlowTestCase):
def testBasic(self):
@@ -2554,13 +2605,17 @@ class ColocationGroupTest(test_util.TensorFlowTestCase):
with self.assertRaises(ValueError):
c.op.get_attr("_class")
- # Roughly test that stack information is being saved correctly for the op.
- locations_dict = b.op._colocation_dict
- self.assertIn("a", locations_dict)
- metadata = locations_dict["a"]
+ def testBasicColocationMetadata(self):
+ const_two = constant_op.constant([2.0], name="two")
+ with ops.colocate_with(const_two.op):
+ const_three = constant_op.constant(3.0, name="three")
+ locations_dict = const_three.op._colocation_dict
+ self.assertIn("two", locations_dict)
+ metadata = locations_dict["two"]
self.assertIsNone(metadata.obj)
- basename = metadata.filename.split("/")[-1]
- self.assertEqual("ops_test.py", basename)
+ # Check that this test's filename is recorded as the file containing the
+ # colocation statement.
+ self.assertEqual("ops_test.py", os.path.basename(metadata.filename))
def testColocationDeviceInteraction(self):
with ops.device("/cpu:0"):
diff --git a/tensorflow/python/framework/tensor_util.py b/tensorflow/python/framework/tensor_util.py
index 8c9dfce7cc..9a0f34fad2 100644
--- a/tensorflow/python/framework/tensor_util.py
+++ b/tensorflow/python/framework/tensor_util.py
@@ -67,10 +67,16 @@ def SlowAppendBFloat16ArrayToTensorProto(tensor_proto, proto_values):
[ExtractBitsFromBFloat16(x) for x in proto_values])
+def FastAppendBFloat16ArrayToTensorProto(tensor_proto, proto_values):
+ fast_tensor_util.AppendBFloat16ArrayToTensorProto(
+ tensor_proto, np.asarray(
+ proto_values, dtype=dtypes.bfloat16.as_numpy_dtype).view(np.uint16))
+
+
if _FAST_TENSOR_UTIL_AVAILABLE:
_NP_TO_APPEND_FN = {
dtypes.bfloat16.as_numpy_dtype:
- SlowAppendBFloat16ArrayToTensorProto,
+ FastAppendBFloat16ArrayToTensorProto,
np.float16:
_MediumAppendFloat16ArrayToTensorProto,
np.float32:
diff --git a/tensorflow/python/framework/test_util_test.py b/tensorflow/python/framework/test_util_test.py
index 122c14c847..f983cbef04 100644
--- a/tensorflow/python/framework/test_util_test.py
+++ b/tensorflow/python/framework/test_util_test.py
@@ -73,7 +73,7 @@ class TestUtilTest(test_util.TensorFlowTestCase):
test_util.assert_equal_graph_def(def_57, def_75)
# Compare two unequal graphs
with self.assertRaisesRegexp(AssertionError,
- r"^Found unexpected node 'seven"):
+ r"^Found unexpected node '{{node seven}}"):
test_util.assert_equal_graph_def(def_57, def_empty)
def testIsGoogleCudaEnabled(self):
diff --git a/tensorflow/python/keras/BUILD b/tensorflow/python/keras/BUILD
index df409d2aa5..0616b29494 100755
--- a/tensorflow/python/keras/BUILD
+++ b/tensorflow/python/keras/BUILD
@@ -778,7 +778,7 @@ py_test(
py_test(
name = "training_test",
- size = "medium",
+ size = "large",
srcs = ["engine/training_test.py"],
srcs_version = "PY2AND3",
tags = ["notsan"],
diff --git a/tensorflow/python/keras/callbacks.py b/tensorflow/python/keras/callbacks.py
index d1b9dc27bd..070d41147d 100644
--- a/tensorflow/python/keras/callbacks.py
+++ b/tensorflow/python/keras/callbacks.py
@@ -31,12 +31,14 @@ import time
import numpy as np
import six
+from tensorflow.python.eager import context
from tensorflow.python.framework import dtypes
from tensorflow.python.keras import backend as K
from tensorflow.python.keras.engine.training_utils import standardize_input_data
from tensorflow.python.keras.utils.generic_utils import Progbar
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import state_ops
+from tensorflow.python.ops import summary_ops_v2
from tensorflow.python.ops import variables
from tensorflow.python.platform import tf_logging as logging
from tensorflow.python.summary import summary as tf_summary
@@ -716,6 +718,15 @@ class TensorBoard(Callback):
`embeddings_layer_names`. Numpy array (if the model has a single
input) or list of Numpy arrays (if the model has multiple inputs).
Learn [more about embeddings](https://www.tensorflow.org/programmers_guide/embedding)
+
+ Raises:
+ ValueError: If histogram_freq is set and no validation data is provided.
+
+ @compatbility(eager)
+ Using `Tensorboard` callback will work while eager execution is enabled,
+ however outputting histogram summaries of weights and gradients is not
+ supported, and thus `histogram_freq` will be ignored.
+ @end_compatibility
"""
# pylint: enable=line-too-long
@@ -734,6 +745,11 @@ class TensorBoard(Callback):
super(TensorBoard, self).__init__()
self.log_dir = log_dir
self.histogram_freq = histogram_freq
+ if self.histogram_freq and context.executing_eagerly():
+ logging.warning(
+ UserWarning('Weight and gradient histograms not supported for eager'
+ 'execution, setting `histogram_freq` to `0`.'))
+ self.histogram_freq = 0
self.merged = None
self.write_graph = write_graph
self.write_grads = write_grads
@@ -741,18 +757,22 @@ class TensorBoard(Callback):
self.batch_size = batch_size
self._current_batch = 0
self._total_batches_seen = 0
- # abstracted writer class to be able to stub for testing
- self._writer_class = tf_summary.FileWriter
self.embeddings_freq = embeddings_freq
self.embeddings_layer_names = embeddings_layer_names
self.embeddings_metadata = embeddings_metadata
self.embeddings_data = embeddings_data
- def set_model(self, model):
- """Sets Keras model and creates summary ops."""
+ def _init_writer(self):
+ """Sets file writer."""
+ if context.executing_eagerly():
+ self.writer = summary_ops_v2.create_file_writer(self.log_dir)
+ elif self.write_graph:
+ self.writer = tf_summary.FileWriter(self.log_dir, K.get_session().graph)
+ else:
+ self.writer = tf_summary.FileWriter(self.log_dir)
- self.model = model
- self.sess = K.get_session()
+ def _make_histogram_ops(self, model):
+ """Defines histogram ops when histogram_freq > 0."""
# only make histogram summary op if it hasn't already been made
if self.histogram_freq and self.merged is None:
for layer in self.model.layers:
@@ -793,8 +813,10 @@ class TensorBoard(Callback):
def is_indexed_slices(grad):
return type(grad).__name__ == 'IndexedSlices'
- grads = [grad.values if is_indexed_slices(grad) else grad
- for grad in grads]
+ grads = [
+ grad.values if is_indexed_slices(grad) else grad
+ for grad in grads
+ ]
tf_summary.histogram('{}_grad'.format(mapped_weight_name), grads)
if hasattr(layer, 'output'):
@@ -803,12 +825,16 @@ class TensorBoard(Callback):
tf_summary.histogram('{}_out_{}'.format(layer.name, i), output)
else:
tf_summary.histogram('{}_out'.format(layer.name), layer.output)
- self.merged = tf_summary.merge_all()
- if self.write_graph:
- self.writer = self._writer_class(self.log_dir, self.sess.graph)
- else:
- self.writer = self._writer_class(self.log_dir)
+ def set_model(self, model):
+ """Sets Keras model and creates summary ops."""
+
+ self.model = model
+ self._init_writer()
+ # histogram summaries only enabled in graph mode
+ if not context.executing_eagerly():
+ self._make_histogram_ops(model)
+ self.merged = tf_summary.merge_all()
# If both embedding_freq and embeddings_data are available, we will
# visualize embeddings.
@@ -894,17 +920,24 @@ class TensorBoard(Callback):
"""
logs = logs or {}
- for name, value in logs.items():
- summary = tf_summary.Summary()
- summary_value = summary.value.add()
- summary_value.simple_value = value.item()
- summary_value.tag = name
- self.writer.add_summary(summary, step)
+ if context.executing_eagerly():
+ # use v2 summary ops
+ with self.writer.as_default(), summary_ops_v2.always_record_summaries():
+ for name, value in logs.items():
+ summary_ops_v2.scalar(name, value.item(), step=step)
+ else:
+ # use FileWriter from v1 summary
+ for name, value in logs.items():
+ summary = tf_summary.Summary()
+ summary_value = summary.value.add()
+ summary_value.simple_value = value.item()
+ summary_value.tag = name
+ self.writer.add_summary(summary, step)
self.writer.flush()
def on_train_begin(self, logs=None):
"""Checks if histogram summaries can be run."""
-
+ # will never be set when in eager
if self.histogram_freq:
if 'validation_steps' in self.params:
self._validation_batches = self.params['validation_steps']
diff --git a/tensorflow/python/keras/callbacks_test.py b/tensorflow/python/keras/callbacks_test.py
index 7d830078ce..d38a753263 100644
--- a/tensorflow/python/keras/callbacks_test.py
+++ b/tensorflow/python/keras/callbacks_test.py
@@ -22,6 +22,7 @@ import csv
import os
import re
import shutil
+import tempfile
import threading
import unittest
@@ -29,10 +30,12 @@ import numpy as np
from tensorflow.core.framework import summary_pb2
from tensorflow.python import keras
+from tensorflow.python.framework import test_util
from tensorflow.python.keras import testing_utils
from tensorflow.python.platform import test
from tensorflow.python.platform import tf_logging as logging
from tensorflow.python.summary.writer import writer_cache
+from tensorflow.python.training import adam
try:
import h5py # pylint:disable=g-import-not-at-top
@@ -63,7 +66,7 @@ class KerasCallbacksTest(test.TestCase):
np.random.seed(1337)
temp_dir = self.get_temp_dir()
- self.addCleanup(shutil.rmtree, temp_dir)
+ self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True)
filepath = os.path.join(temp_dir, 'checkpoint.h5')
(x_train, y_train), (x_test, y_test) = testing_utils.get_test_data(
@@ -479,7 +482,7 @@ class KerasCallbacksTest(test.TestCase):
with self.test_session():
np.random.seed(1337)
temp_dir = self.get_temp_dir()
- self.addCleanup(shutil.rmtree, temp_dir)
+ self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True)
filepath = os.path.join(temp_dir, 'log.tsv')
sep = '\t'
@@ -557,7 +560,7 @@ class KerasCallbacksTest(test.TestCase):
# does not result in invalid CSVs.
np.random.seed(1337)
tmpdir = self.get_temp_dir()
- self.addCleanup(shutil.rmtree, tmpdir)
+ self.addCleanup(shutil.rmtree, tmpdir, ignore_errors=True)
with self.test_session():
fp = os.path.join(tmpdir, 'test.csv')
@@ -649,7 +652,7 @@ class KerasCallbacksTest(test.TestCase):
np.random.seed(1337)
temp_dir = self.get_temp_dir()
- self.addCleanup(shutil.rmtree, temp_dir)
+ self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True)
(x_train, y_train), (x_test, y_test) = testing_utils.get_test_data(
train_samples=TRAIN_SAMPLES,
@@ -747,7 +750,7 @@ class KerasCallbacksTest(test.TestCase):
def test_TensorBoard_histogram_freq_must_have_validation_data(self):
np.random.seed(1337)
tmpdir = self.get_temp_dir()
- self.addCleanup(shutil.rmtree, tmpdir)
+ self.addCleanup(shutil.rmtree, tmpdir, ignore_errors=True)
with self.test_session():
filepath = os.path.join(tmpdir, 'logs')
@@ -819,7 +822,7 @@ class KerasCallbacksTest(test.TestCase):
def test_TensorBoard_multi_input_output(self):
np.random.seed(1337)
tmpdir = self.get_temp_dir()
- self.addCleanup(shutil.rmtree, tmpdir)
+ self.addCleanup(shutil.rmtree, tmpdir, ignore_errors=True)
with self.test_session():
filepath = os.path.join(tmpdir, 'logs')
@@ -917,9 +920,12 @@ class KerasCallbacksTest(test.TestCase):
def close(self):
pass
+ def _init_writer(obj):
+ obj.writer = FileWriterStub(obj.log_dir)
+
np.random.seed(1337)
tmpdir = self.get_temp_dir()
- self.addCleanup(shutil.rmtree, tmpdir)
+ self.addCleanup(shutil.rmtree, tmpdir, ignore_errors=True)
(x_train, y_train), (x_test, y_test) = testing_utils.get_test_data(
train_samples=TRAIN_SAMPLES,
test_samples=TEST_SAMPLES,
@@ -940,13 +946,13 @@ class KerasCallbacksTest(test.TestCase):
loss='categorical_crossentropy',
optimizer='sgd',
metrics=['accuracy'])
+ keras.callbacks.TensorBoard._init_writer = _init_writer
tsb = keras.callbacks.TensorBoard(
log_dir=tmpdir,
histogram_freq=1,
write_images=True,
write_grads=True,
batch_size=5)
- tsb._writer_class = FileWriterStub
cbks = [tsb]
# fit with validation data
@@ -964,7 +970,7 @@ class KerasCallbacksTest(test.TestCase):
def test_Tensorboard_histogram_summaries_with_generator(self):
np.random.seed(1337)
tmpdir = self.get_temp_dir()
- self.addCleanup(shutil.rmtree, tmpdir)
+ self.addCleanup(shutil.rmtree, tmpdir, ignore_errors=True)
def generator():
x = np.random.randn(10, 100).astype(np.float32)
@@ -1061,7 +1067,7 @@ class KerasCallbacksTest(test.TestCase):
def test_TensorBoard_with_ReduceLROnPlateau(self):
with self.test_session():
temp_dir = self.get_temp_dir()
- self.addCleanup(shutil.rmtree, temp_dir)
+ self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True)
(x_train, y_train), (x_test, y_test) = testing_utils.get_test_data(
train_samples=TRAIN_SAMPLES,
@@ -1118,11 +1124,11 @@ class KerasCallbacksTest(test.TestCase):
def close(self):
pass
- logdir = 'fake_dir'
+ temp_dir = self.get_temp_dir()
+ self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True)
- # log every batch
- tb_cbk = keras.callbacks.TensorBoard(logdir)
- tb_cbk.writer = FileWriterStub(logdir)
+ tb_cbk = keras.callbacks.TensorBoard(temp_dir)
+ tb_cbk.writer = FileWriterStub(temp_dir)
for batch in range(5):
tb_cbk.on_batch_end(batch, {'acc': np.float32(batch)})
@@ -1150,10 +1156,11 @@ class KerasCallbacksTest(test.TestCase):
def close(self):
pass
- logdir = 'fake_dir'
+ temp_dir = self.get_temp_dir()
+ self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True)
- tb_cbk = keras.callbacks.TensorBoard(logdir)
- tb_cbk.writer = FileWriterStub(logdir)
+ tb_cbk = keras.callbacks.TensorBoard(temp_dir)
+ tb_cbk.writer = FileWriterStub(temp_dir)
tb_cbk.on_batch_end(0, {'acc': np.float32(5.0)})
tb_cbk.on_epoch_end(0, {'acc': np.float32(10.0)})
@@ -1164,6 +1171,43 @@ class KerasCallbacksTest(test.TestCase):
self.assertEqual(epoch_step, 0)
self.assertEqual(epoch_summary.value[0].simple_value, 10.0)
+ @test_util.run_in_graph_and_eager_modes
+ def test_Tensorboard_eager(self):
+ with self.test_session():
+ temp_dir = tempfile.mkdtemp(dir=self.get_temp_dir())
+ self.addCleanup(shutil.rmtree, temp_dir, ignore_errors=True)
+
+ (x_train, y_train), (x_test, y_test) = testing_utils.get_test_data(
+ train_samples=TRAIN_SAMPLES,
+ test_samples=TEST_SAMPLES,
+ input_shape=(INPUT_DIM,),
+ num_classes=NUM_CLASSES)
+ y_test = keras.utils.to_categorical(y_test)
+ y_train = keras.utils.to_categorical(y_train)
+
+ model = keras.models.Sequential()
+ model.add(
+ keras.layers.Dense(
+ NUM_HIDDEN, input_dim=INPUT_DIM, activation='relu'))
+ model.add(keras.layers.Dense(NUM_CLASSES, activation='softmax'))
+ model.compile(
+ loss='binary_crossentropy',
+ optimizer=adam.AdamOptimizer(0.01),
+ metrics=['accuracy'])
+
+ cbks = [keras.callbacks.TensorBoard(log_dir=temp_dir)]
+
+ model.fit(
+ x_train,
+ y_train,
+ batch_size=BATCH_SIZE,
+ validation_data=(x_test, y_test),
+ callbacks=cbks,
+ epochs=2,
+ verbose=0)
+
+ self.assertTrue(os.path.exists(temp_dir))
+
def test_RemoteMonitorWithJsonPayload(self):
if requests is None:
self.skipTest('`requests` required to run this test')
diff --git a/tensorflow/python/keras/engine/base_layer.py b/tensorflow/python/keras/engine/base_layer.py
index b41f6ee03b..e1214f8103 100644
--- a/tensorflow/python/keras/engine/base_layer.py
+++ b/tensorflow/python/keras/engine/base_layer.py
@@ -26,6 +26,7 @@ import numpy as np
from six.moves import zip # pylint: disable=redefined-builtin
from tensorflow.python.eager import context
+from tensorflow.python.eager import function as eager_function
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import ops
from tensorflow.python.framework import tensor_shape
@@ -174,6 +175,12 @@ class Layer(checkpointable.CheckpointableBase):
self.supports_masking = False
+ call_argspec = tf_inspect.getargspec(self.call)
+ if 'training' in call_argspec.args:
+ self._expects_training_arg = True
+ else:
+ self._expects_training_arg = False
+
# Manage input shape information if passed.
if 'input_shape' in kwargs or 'batch_input_shape' in kwargs:
# In this case we will later create an input layer
@@ -786,17 +793,8 @@ class Layer(checkpointable.CheckpointableBase):
if hasattr(self, '_initial_weights') and self._initial_weights is not None:
self.set_weights(self._initial_weights)
del self._initial_weights
- self._post_build_cleanup()
return outputs
- def _post_build_cleanup(self):
- """Hooks to run after all sub-Layers are built."""
- # Note that in addition to Layer.__call__, this method is called by Model
- # after building a graph network (which skips __call__). It should be called
- # when possible if self.built may have switched from False to True, and is
- # idempotent.
- pass # No-op for Layers which don't override this method.
-
def apply(self, inputs, *args, **kwargs):
"""Apply the layer on a input.
@@ -970,6 +968,39 @@ class Layer(checkpointable.CheckpointableBase):
Returns:
An input shape tuple.
"""
+ if context.executing_eagerly():
+ # In this case we build the model first in order to do shape inference.
+ # This is acceptable because the framework only calls
+ # `compute_output_shape` on shape values that the layer would later be
+ # built for. It would however cause issues in case a user attempts to
+ # use `compute_output_shape` manually (these users will have to
+ # implement `compute_output_shape` themselves).
+ self.build(input_shape)
+
+ with context.graph_mode():
+ graph = eager_function.CapturingGraph()
+ with graph.as_default():
+ if isinstance(input_shape, list):
+ inputs = [generate_placeholders_from_shape(shape)
+ for shape in input_shape]
+ else:
+ inputs = generate_placeholders_from_shape(input_shape)
+
+ try:
+ if self._expects_training_arg:
+ outputs = self(inputs, training=False)
+ else:
+ outputs = self(inputs)
+ except TypeError:
+ raise NotImplementedError('We could not automatically infer '
+ 'the static shape of the layer\'s output.'
+ ' Please implement the '
+ '`compute_output_shape` method on your '
+ 'layer (%s).' % self.__class__.__name__)
+ if isinstance(outputs, list):
+ return [output.shape for output in outputs]
+ else:
+ return outputs.shape
raise NotImplementedError
def compute_mask(self, inputs, mask=None): # pylint: disable=unused-argument
@@ -1925,3 +1956,18 @@ def make_variable(name,
synchronization=synchronization,
aggregation=aggregation)
return v
+
+
+def generate_dummy_data_from_shape(shape):
+ if isinstance(shape, tensor_shape.TensorShape):
+ shape = shape.as_list()
+
+ # Replace Nones in input shape with dummy `1` value
+ shape = [x.value if isinstance(x, tensor_shape.Dimension) else x
+ for x in shape]
+ shape = [1 if x is None else x for x in shape]
+ return array_ops.ones(shape, dtype=backend.floatx())
+
+
+def generate_placeholders_from_shape(shape):
+ return array_ops.placeholder(shape=shape, dtype=backend.floatx())
diff --git a/tensorflow/python/keras/engine/network.py b/tensorflow/python/keras/engine/network.py
index 752e9963ca..20a29dbf20 100644
--- a/tensorflow/python/keras/engine/network.py
+++ b/tensorflow/python/keras/engine/network.py
@@ -20,7 +20,6 @@ from __future__ import division
from __future__ import print_function
import copy
-import functools
import json
import os
import weakref
@@ -30,6 +29,7 @@ from six.moves import zip # pylint: disable=redefined-builtin
from tensorflow.python import pywrap_tensorflow
from tensorflow.python.eager import context
+from tensorflow.python.framework import errors
from tensorflow.python.framework import errors_impl
from tensorflow.python.framework import ops
from tensorflow.python.framework import tensor_shape
@@ -144,10 +144,6 @@ class Network(base_layer.Layer):
self._checkpointable_saver = checkpointable_utils.CheckpointableSaver(
weakref.ref(self))
- # A zero-argument function which should be called and set back to None as
- # soon as the network is built (only applicable to subclassed Models). Runs
- # restore operations when graph building.
- self._in_progress_restore_finalizer = None
@checkpointable.no_automatic_dependency_tracking
def _init_graph_network(self, inputs, outputs, name=None):
@@ -739,6 +735,86 @@ class Network(base_layer.Layer):
return specs[0]
return specs
+ def build(self, input_shape):
+ """Builds the model based on input shapes received.
+
+ This is to be used for subclassed models, which do not know at instantiation
+ time what their inputs look like.
+
+ Args:
+ input_shape: Single tuple, TensorShape, or list of shapes, where shapes
+ are tuples, integers, or TensorShapes.
+
+ Raises:
+ ValueError:
+ 1. In case of invalid user-provided data (not of type tuple,
+ list, or TensorShape).
+ 2. If the model requires call arguments that are agnostic
+ to the input shapes (positional or kwarg in call signature).
+ 3. If not all layers were properly built.
+ 4. If float type inputs are not supported within the layers.
+
+ In each of these cases, the user should build their model by calling it
+ on real tensor data.
+ """
+ if self._is_graph_network:
+ self.built = True
+ return
+
+ # If subclass network
+ if input_shape is None:
+ raise ValueError('Input shape must be defined when calling build on a '
+ 'model subclass network.')
+ valid_types = (tuple, list, tensor_shape.TensorShape)
+ if not isinstance(input_shape, valid_types):
+ raise ValueError('Specified input shape is not one of the valid types. '
+ 'Please specify a batch input shape of type tuple or '
+ 'list of input shapes. User provided '
+ 'input type: {}'.format(type(input_shape)))
+
+ if input_shape and not self.inputs:
+ if isinstance(input_shape, list):
+ # List of input shapes
+ x = [base_layer.generate_dummy_data_from_shape(shape)
+ for shape in input_shape]
+ else:
+ x = base_layer.generate_dummy_data_from_shape(input_shape)
+
+ kwargs = {}
+ num_call_args = len(tf_inspect.getargspec(self.call).args)
+ if self._expects_training_arg and num_call_args == 3:
+ # Has call signature of call(self, input, training)
+ kwargs['training'] = False
+ elif num_call_args > 2:
+ # Has invalid call signature of call(self, input, *args, **kwargs)
+ raise ValueError('Currently, you cannot build your model if it has '
+ 'positional or keyword arguments that are not '
+ 'inputs to the model, but are required for its '
+ '`call` method. Instead, in order to instantiate '
+ 'and build your model, `call` your model on real '
+ 'tensor data with all expected call arguments.')
+
+ try:
+ self.call(x, **kwargs)
+ except (errors.InvalidArgumentError, TypeError):
+ raise ValueError('You cannot build your model by calling `build` '
+ 'if your layers do not support float type inputs. '
+ 'Instead, in order to instantiate and build your '
+ 'model, `call` your model on real tensor data (of '
+ 'the correct dtype).')
+
+ if self._layers:
+ self._track_layers(self._layers)
+ if self.layers:
+ for layer in self.layers:
+ if not layer.built:
+ raise ValueError('Layer: {} was not built in your model. Calling '
+ '`build` manually on a subclassed model is only '
+ 'allowed for models with a static topology. '
+ 'In this case, you can build your model by '
+ 'calling it on real tensor data.'.format(layer))
+ self.built = True
+
def call(self, inputs, training=None, mask=None):
"""Calls the model on new inputs.
@@ -779,6 +855,8 @@ class Network(base_layer.Layer):
def compute_output_shape(self, input_shape):
if not self._is_graph_network:
+ if context.executing_eagerly():
+ return super(Network, self).compute_output_shape(input_shape)
raise NotImplementedError
if isinstance(input_shape, list):
@@ -889,7 +967,7 @@ class Network(base_layer.Layer):
mask: List of masks (tensors or None).
Returns:
- Three lists: output_tensors, output_masks, output_shapes
+ Two lists: output_tensors, output_masks
"""
# Note: masking support is relevant mainly for Keras.
# It cannot be factored out without having the fully reimplement the network
@@ -956,7 +1034,6 @@ class Network(base_layer.Layer):
else:
output_masks = [None for _ in output_tensors]
computed_tensors = [computed_tensor]
- computed_masks = [computed_mask]
else:
computed_tensors = [x[0] for x in computed_data]
computed_masks = [x[1] for x in computed_data]
@@ -1423,13 +1500,9 @@ class Network(base_layer.Layer):
'load_weights).')
if not context.executing_eagerly():
session = backend.get_session()
- finalizer = functools.partial(status.run_restore_ops, session=session)
- if self.built:
- finalizer()
- else:
- # Hold on to this status object until the network is built (for
- # subclassed Models). Then we'll run restore ops if necessary.
- self._in_progress_restore_finalizer = finalizer
+ # Restore existing variables (if any) immediately, and set up a
+ # streaming restore for any variables created in the future.
+ checkpointable_utils.streaming_restore(status=status, session=session)
return status
if h5py is None:
raise ImportError(
@@ -1447,14 +1520,6 @@ class Network(base_layer.Layer):
else:
saving.load_weights_from_hdf5_group(f, self.layers)
- def _post_build_cleanup(self):
- super(Network, self)._post_build_cleanup()
- if self._in_progress_restore_finalizer is not None:
- # Runs queued restore operations left over from load_weights when graph
- # building.
- self._in_progress_restore_finalizer()
- self._in_progress_restore_finalizer = None
-
def _updated_config(self):
"""Util shared between different serialization methods.
diff --git a/tensorflow/python/keras/engine/saving_test.py b/tensorflow/python/keras/engine/saving_test.py
index 030328f2a6..e029e614e0 100644
--- a/tensorflow/python/keras/engine/saving_test.py
+++ b/tensorflow/python/keras/engine/saving_test.py
@@ -722,18 +722,23 @@ class TestWeightSavingAndLoadingTFFormat(test.TestCase):
self.assertEqual(len(graph.get_operations()), op_count)
def _weight_loading_test_template(self, make_model_fn):
- with self.test_session() as session:
+ with self.test_session():
model = make_model_fn()
+ model.compile(
+ loss='mse',
+ optimizer=training_module.RMSPropOptimizer(0.1),
+ metrics=['acc'])
temp_dir = self.get_temp_dir()
prefix = os.path.join(temp_dir, 'ckpt')
+ train_x = np.random.random((3, 2))
+ train_y = np.random.random((3,))
+ x = constant_op.constant(train_x, dtype=dtypes.float32)
- x = constant_op.constant(np.random.random((3, 2)), dtype=dtypes.float32)
- executing_eagerly = context.executing_eagerly()
- ref_y_tensor = model(x)
- if not executing_eagerly:
- session.run([v.initializer for v in model.variables])
- ref_y = self.evaluate(ref_y_tensor)
+ model.train_on_batch(train_x, train_y)
model.save_weights(prefix, save_format='tf')
+ ref_y_before_train = model.predict(train_x)
+ model.train_on_batch(train_x, train_y)
+ ref_y_after_train = model.predict(train_x)
for v in model.variables:
self.evaluate(
v.assign(random_ops.random_normal(shape=array_ops.shape(v))))
@@ -741,16 +746,27 @@ class TestWeightSavingAndLoadingTFFormat(test.TestCase):
self.addCleanup(shutil.rmtree, temp_dir)
model.load_weights(prefix)
- y = self.evaluate(model(x))
- self.assertAllClose(ref_y, y)
+ self.assertAllClose(ref_y_before_train, self.evaluate(model(x)))
# Test restore-on-create if this is a subclassed Model (graph Networks
# will have already created their variables).
load_model = make_model_fn()
load_model.load_weights(prefix)
- restore_on_create_y_tensor = load_model(x)
- restore_on_create_y = self.evaluate(restore_on_create_y_tensor)
- self.assertAllClose(ref_y, restore_on_create_y)
+ self.assertAllClose(
+ ref_y_before_train,
+ self.evaluate(load_model(x)))
+ load_model = make_model_fn()
+ load_model.load_weights(prefix)
+ # We need to run some of the restore ops for predict(), but not all
+ # variables have been created yet (optimizer slot variables). Tests
+ # incremental restore.
+ load_model.predict(train_x)
+ load_model.compile(
+ loss='mse',
+ optimizer=training_module.RMSPropOptimizer(0.1),
+ metrics=['acc'])
+ load_model.train_on_batch(train_x, train_y)
+ self.assertAllClose(ref_y_after_train, self.evaluate(load_model(x)))
@test_util.run_in_graph_and_eager_modes
def test_weight_loading_graph_model(self):
@@ -858,5 +874,6 @@ class TestWeightSavingAndLoadingTFFormat(test.TestCase):
SubclassedModel, SubclassedModelRestore,
_restore_init_fn)
+
if __name__ == '__main__':
test.main()
diff --git a/tensorflow/python/keras/engine/topology_test.py b/tensorflow/python/keras/engine/topology_test.py
index 3eb69bd7f3..34f74db6ef 100644
--- a/tensorflow/python/keras/engine/topology_test.py
+++ b/tensorflow/python/keras/engine/topology_test.py
@@ -110,7 +110,6 @@ class TopologyConstructionTest(test.TestCase):
layer = keras.layers.BatchNormalization()
_ = layer.apply(x1)
- print('BN updates', layer._updates)
self.assertEqual(len(layer.updates), 2)
self.assertEqual(len(layer.get_updates_for(x1)), 2)
self.assertEqual(len(layer.get_updates_for(None)), 0)
@@ -960,9 +959,6 @@ class DeferredModeTest(test.TestCase):
def call(self, inputs):
return inputs[0] + inputs[1]
- def compute_output_shape(self, input_shape):
- return input_shape[0]
-
c = AddLayer()([a, input_b]) # pylint: disable=not-callable
c = keras.layers.Dense(2)(c)
@@ -978,6 +974,101 @@ class DeferredModeTest(test.TestCase):
self.assertEqual(outputs[1].shape.as_list(), [10, 2])
+class DefaultShapeInferenceBehaviorTest(test.TestCase):
+
+ def _testShapeInference(self, model, input_shape, expected_output_shape):
+ input_value = np.random.random(input_shape)
+ output_value = model.predict(input_value)
+ self.assertEqual(output_value.shape, expected_output_shape)
+
+ @test_util.run_in_graph_and_eager_modes()
+ def testSingleInputCase(self):
+
+ class LayerWithOneInput(keras.layers.Layer):
+
+ def build(self, input_shape):
+ self.w = array_ops.ones(shape=(3, 4))
+
+ def call(self, inputs):
+ return keras.backend.dot(inputs, self.w)
+
+ inputs = input_layer_lib.Input(shape=(3,))
+ layer = LayerWithOneInput()
+
+ if context.executing_eagerly():
+ self.assertEqual(
+ layer.compute_output_shape((None, 3)).as_list(), [None, 4])
+ # As a side-effect, compute_output_shape builds the layer.
+ self.assertTrue(layer.built)
+ # We can still query the layer's compute_output_shape with compatible
+ # input shapes.
+ self.assertEqual(
+ layer.compute_output_shape((6, 3)).as_list(), [6, 4])
+
+ outputs = layer(inputs)
+ model = keras.Model(inputs, outputs)
+ self._testShapeInference(model, (2, 3), (2, 4))
+
+ @test_util.run_in_graph_and_eager_modes()
+ def testMultiInputOutputCase(self):
+
+ class MultiInputOutputLayer(keras.layers.Layer):
+
+ def build(self, input_shape):
+ self.w = array_ops.ones(shape=(3, 4))
+
+ def call(self, inputs):
+ a = keras.backend.dot(inputs[0], self.w)
+ b = a + inputs[1]
+ return [a, b]
+
+ input_a = input_layer_lib.Input(shape=(3,))
+ input_b = input_layer_lib.Input(shape=(4,))
+ output_a, output_b = MultiInputOutputLayer()([input_a, input_b])
+ model = keras.Model([input_a, input_b], [output_a, output_b])
+ output_a_val, output_b_val = model.predict(
+ [np.random.random((2, 3)), np.random.random((2, 4))])
+ self.assertEqual(output_a_val.shape, (2, 4))
+ self.assertEqual(output_b_val.shape, (2, 4))
+
+ @test_util.run_in_graph_and_eager_modes()
+ def testTrainingArgument(self):
+
+ class LayerWithTrainingArg(keras.layers.Layer):
+
+ def build(self, input_shape):
+ self.w = array_ops.ones(shape=(3, 4))
+
+ def call(self, inputs, training):
+ return keras.backend.dot(inputs, self.w)
+
+ inputs = input_layer_lib.Input(shape=(3,))
+ outputs = LayerWithTrainingArg()(inputs, training=False)
+ model = keras.Model(inputs, outputs)
+ self._testShapeInference(model, (2, 3), (2, 4))
+
+ @test_util.run_in_graph_and_eager_modes()
+ def testUnsupportedSignature(self):
+
+ class LayerWithAdditionalArg(keras.layers.Layer):
+
+ def build(self, input_shape):
+ self.w = array_ops.ones(shape=(3, 4))
+
+ def call(self, inputs, some_arg):
+ return keras.backend.dot(inputs, self.w) + some_arg
+
+ inputs = input_layer_lib.Input(shape=(3,))
+ if context.executing_eagerly():
+ with self.assertRaises(NotImplementedError):
+ outputs = LayerWithAdditionalArg()(inputs, some_arg=0)
+ else:
+ # Works with graph mode because the graph of ops is built together with
+ # the graph of layers.
+ outputs = LayerWithAdditionalArg()(inputs, some_arg=0)
+ _ = keras.Model(inputs, outputs)
+
+
class GraphUtilsTest(test.TestCase):
def testGetReachableFromInputs(self):
diff --git a/tensorflow/python/keras/engine/training.py b/tensorflow/python/keras/engine/training.py
index 4df739254b..c8e5e497ae 100644
--- a/tensorflow/python/keras/engine/training.py
+++ b/tensorflow/python/keras/engine/training.py
@@ -24,14 +24,11 @@ import numpy as np
from tensorflow.python.data.ops import dataset_ops
from tensorflow.python.data.ops import iterator_ops
from tensorflow.python.eager import context
-from tensorflow.python.framework import constant_op
from tensorflow.python.framework import errors
from tensorflow.python.framework import ops
-from tensorflow.python.framework import tensor_shape
from tensorflow.python.framework import tensor_util
from tensorflow.python.keras import backend as K
from tensorflow.python.keras import losses
-from tensorflow.python.keras import metrics as metrics_module
from tensorflow.python.keras import optimizers
from tensorflow.python.keras.engine import base_layer
from tensorflow.python.keras.engine import training_arrays
@@ -40,11 +37,9 @@ from tensorflow.python.keras.engine import training_generator
from tensorflow.python.keras.engine import training_utils
from tensorflow.python.keras.engine.network import Network
from tensorflow.python.keras.utils.generic_utils import slice_arrays
-from tensorflow.python.ops import array_ops
from tensorflow.python.platform import tf_logging as logging
from tensorflow.python.training import optimizer as tf_optimizer_module
from tensorflow.python.training.checkpointable import base as checkpointable
-from tensorflow.python.util import tf_inspect
from tensorflow.python.util.tf_export import tf_export
@@ -118,6 +113,24 @@ class Model(Network):
# Create a cache for dataset - uninitialized iterators
self._dataset_iterator_cache = weakref.WeakKeyDictionary()
+ def _set_sample_weight_attributes(self, sample_weight_mode,
+ skip_target_weighing_indices):
+ """Sets sample weight related attributes on the model."""
+ sample_weights, sample_weight_modes = training_utils.prepare_sample_weights(
+ self.output_names, sample_weight_mode, skip_target_weighing_indices)
+ self.sample_weights = sample_weights
+ self.sample_weight_modes = sample_weight_modes
+ self._feed_sample_weight_modes = [
+ sample_weight_modes[i]
+ for i in range(len(self.outputs))
+ if i not in skip_target_weighing_indices
+ ]
+ self._feed_sample_weights = [
+ sample_weights[i]
+ for i in range(len(sample_weights))
+ if i not in skip_target_weighing_indices
+ ]
+
@checkpointable.no_automatic_dependency_tracking
def compile(self,
optimizer,
@@ -190,8 +203,6 @@ class Model(Network):
self.loss = loss
self.metrics = metrics or []
self.loss_weights = loss_weights
- if context.executing_eagerly() and sample_weight_mode is not None:
- raise ValueError('sample_weight_mode is not supported in Eager mode.')
self.sample_weight_mode = sample_weight_mode
if context.executing_eagerly() and weighted_metrics is not None:
raise ValueError('weighted_metrics is not supported in Eager mode.')
@@ -282,8 +293,12 @@ class Model(Network):
str(loss_weights) + ' - expected a list of dicts.')
self.loss_weights_list = loss_weights_list
- # initialization for Eager mode execution
+ # Initialization for Eager mode execution.
if context.executing_eagerly():
+ # Prepare sample weights.
+ self._set_sample_weight_attributes(sample_weight_mode,
+ skip_target_weighing_indices)
+
if target_tensors is not None:
raise ValueError('target_tensors are not currently supported in Eager '
'mode.')
@@ -301,10 +316,6 @@ class Model(Network):
with K.name_scope('metrics'):
training_utils.populate_metric_names(self)
- self._feed_sample_weight_modes = []
- for i in range(len(self.outputs)):
- self._feed_sample_weight_modes.append(None)
- self.sample_weights = []
self.targets = []
for i in range(len(self.outputs)):
self._feed_output_names.append(self.output_names[i])
@@ -364,73 +375,8 @@ class Model(Network):
self.targets.append(target)
# Prepare sample weights.
- sample_weights = []
- sample_weight_modes = []
- if isinstance(sample_weight_mode, dict):
- for name in sample_weight_mode:
- if name not in self.output_names:
- raise ValueError(
- 'Unknown entry in '
- 'sample_weight_mode dictionary: "' + name + '". '
- 'Only expected the following keys: ' + str(self.output_names))
- for i, name in enumerate(self.output_names):
- if i in skip_target_weighing_indices:
- weight = None
- sample_weight_modes.append(None)
- else:
- if name not in sample_weight_mode:
- raise ValueError(
- 'Output "' + name + '" missing from sample_weight_modes '
- 'dictionary')
- if sample_weight_mode.get(name) == 'temporal':
- weight = K.placeholder(ndim=2, name=name + '_sample_weights')
- sample_weight_modes.append('temporal')
- else:
- weight = K.placeholder(ndim=1, name=name + 'sample_weights')
- sample_weight_modes.append(None)
- sample_weights.append(weight)
- elif isinstance(sample_weight_mode, list):
- if len(sample_weight_mode) != len(self.outputs):
- raise ValueError('When passing a list as sample_weight_mode, '
- 'it should have one entry per model output. '
- 'The model has ' + str(len(self.outputs)) +
- ' outputs, but you passed '
- 'sample_weight_mode=' + str(sample_weight_mode))
- for i in range(len(self.output_names)):
- if i in skip_target_weighing_indices:
- weight = None
- sample_weight_modes.append(None)
- else:
- mode = sample_weight_mode[i]
- name = self.output_names[i]
- if mode == 'temporal':
- weight = K.placeholder(ndim=2, name=name + '_sample_weights')
- sample_weight_modes.append('temporal')
- else:
- weight = K.placeholder(ndim=1, name=name + '_sample_weights')
- sample_weight_modes.append(None)
- sample_weights.append(weight)
- else:
- for i, name in enumerate(self.output_names):
- if i in skip_target_weighing_indices:
- sample_weight_modes.append(None)
- sample_weights.append(None)
- else:
- if sample_weight_mode == 'temporal':
- sample_weights.append(array_ops.placeholder_with_default(
- constant_op.constant([[1.]], dtype=K.floatx()),
- shape=[None, None], name=name + '_sample_weights'))
- sample_weight_modes.append('temporal')
- else:
- sample_weights.append(array_ops.placeholder_with_default(
- constant_op.constant([1.], dtype=K.floatx()),
- shape=[None], name=name + '_sample_weights'))
- sample_weight_modes.append(None)
- self.sample_weight_modes = sample_weight_modes
- self._feed_sample_weight_modes = []
- for i in range(len(self.outputs)):
- if i not in skip_target_weighing_indices:
- self._feed_sample_weight_modes.append(self.sample_weight_modes[i])
+ self._set_sample_weight_attributes(sample_weight_mode,
+ skip_target_weighing_indices)
# Prepare metrics.
self.weighted_metrics = weighted_metrics
@@ -446,7 +392,7 @@ class Model(Network):
y_true = self.targets[i]
y_pred = self.outputs[i]
weighted_loss = weighted_losses[i]
- sample_weight = sample_weights[i]
+ sample_weight = self.sample_weights[i]
mask = masks[i]
loss_weight = loss_weights_list[i]
with K.name_scope(self.output_names[i] + '_loss'):
@@ -485,50 +431,28 @@ class Model(Network):
y_true = self.targets[i]
y_pred = self.outputs[i]
- weights = sample_weights[i]
+ weights = self.sample_weights[i]
output_metrics = nested_metrics[i]
output_weighted_metrics = nested_weighted_metrics[i]
+ output_shape = self.outputs[i].get_shape().as_list()
+ loss_fn = self.loss_functions[i]
- def handle_metrics(metrics, weights=None):
+ def handle_metrics(metrics, output_shape, loss_fn, weights=None):
+ """Invokes metric functions for the output."""
for metric in metrics:
- if metric in ('accuracy', 'acc', 'crossentropy', 'ce'):
- # custom handling of accuracy/crossentropy
- # (because of class mode duality)
- output_shape = self.outputs[i].get_shape().as_list()
- if (output_shape[-1] == 1 or
- self.loss_functions[i] == losses.binary_crossentropy):
- # case: binary accuracy/crossentropy
- if metric in ('accuracy', 'acc'):
- metric_fn = metrics_module.binary_accuracy
- elif metric in ('crossentropy', 'ce'):
- metric_fn = metrics_module.binary_crossentropy
- elif self.loss_functions[
- i] == losses.sparse_categorical_crossentropy:
- # case: categorical accuracy/crossentropy with sparse targets
- if metric in ('accuracy', 'acc'):
- metric_fn = metrics_module.sparse_categorical_accuracy
- elif metric in ('crossentropy', 'ce'):
- metric_fn = metrics_module.sparse_categorical_crossentropy
- else:
- # case: categorical accuracy/crossentropy
- if metric in ('accuracy', 'acc'):
- metric_fn = metrics_module.categorical_accuracy
- elif metric in ('crossentropy', 'ce'):
- metric_fn = metrics_module.categorical_crossentropy
- weighted_metric_fn = training_utils.weighted_masked_objective(
- metric_fn)
- else:
- metric_fn = metrics_module.get(metric)
- weighted_metric_fn = training_utils.weighted_masked_objective(
- metric_fn)
- metric_name = training_utils.get_base_metric_name(
+ metric_fn = training_utils.get_metric_function(
+ metric, output_shape=output_shape, loss_fn=loss_fn)
+ metric_name = training_utils.get_metric_name(
metric, weighted=weights is not None)
+
with K.name_scope(metric_name):
+ weighted_metric_fn = training_utils.weighted_masked_objective(
+ metric_fn)
metric_result = weighted_metric_fn(
- y_true, y_pred, weights=weights, mask=masks[i])
+ y_true, y_pred, weights=weights, mask=masks[i]) # pylint: disable=undefined-loop-variable
- training_utils.add_metric_name(self, metric_name, i)
+ metric_name = training_utils.add_metric_name(self, metric_name, i) # pylint: disable=undefined-loop-variable
self.metrics_tensors.append(metric_result)
# Keep track of state updates created by
@@ -538,16 +462,12 @@ class Model(Network):
self.stateful_metric_functions.append(metric_fn)
self.metrics_updates += metric_fn.updates
- handle_metrics(output_metrics)
- handle_metrics(output_weighted_metrics, weights=weights)
+ handle_metrics(output_metrics, output_shape, loss_fn)
+ handle_metrics(
+ output_weighted_metrics, output_shape, loss_fn, weights=weights)
# Prepare gradient updates and state updates.
self.total_loss = total_loss
- self.sample_weights = sample_weights
- self._feed_sample_weights = []
- for i in range(len(self.sample_weights)):
- if i not in skip_target_weighing_indices:
- self._feed_sample_weights.append(self.sample_weights[i])
# Functions for train, test and predict will
# be compiled lazily when required.
@@ -562,95 +482,6 @@ class Model(Network):
trainable_weights = self.trainable_weights
self._collected_trainable_weights = trainable_weights
- def build(self, input_shape):
- """Build the model based on input shapes received.
-
- This is to be used for subclassed models, which do not know at instantiation
- time what their inputs look like.
-
- Args:
- input_shape: Single tuple, TensorShape, or list of shapes, where shapes
- are tuples, integers, or TensorShapes.
-
- Raises:
- ValueError:
- 1. In case of invalid user-provided data (not of type tuple,
- list, or TensorShape).
- 2. If the model requires call arguments that are agnostic
- to the input shapes (positional or kwarg in call signature).
- 3. If not all layers were properly built.
- 4. If float type inputs are not supported within the layers.
-
- In each of these cases, the user should build their model by calling it
- on real tensor data.
- """
- if self._is_graph_network:
- self.built = True
- return
-
- # If subclass network
- if input_shape is None:
- raise ValueError('Input shape must be defined when calling build on a '
- 'model subclass network.')
- valid_types = (tuple, list, tensor_shape.TensorShape)
- if not isinstance(input_shape, valid_types):
- raise ValueError('Specified input shape is not one of the valid types. '
- 'Please specify a batch input shape of type tuple or '
- 'list of input shapes. User provided '
- 'input type: {}'.format(type(input_shape)))
-
- def _generate_dummy_data_from_shape(shape):
- if isinstance(shape, tensor_shape.TensorShape):
- shape = shape.as_list()
-
- # Replace Nones in input shape with dummy `1` value
- shape = [x.value if isinstance(x, tensor_shape.Dimension) else x
- for x in shape]
- shape = [1 if x is None else x for x in shape]
- return array_ops.ones(shape, dtype=K.floatx())
-
- if input_shape and not self.inputs:
- if isinstance(input_shape, list):
- # List of input shapes
- x = [_generate_dummy_data_from_shape(shape) for shape in input_shape]
- else:
- x = _generate_dummy_data_from_shape(input_shape)
-
- kwargs = {}
- num_call_args = len(tf_inspect.getargspec(self.call).args)
- if self._expects_training_arg and num_call_args == 3:
- # Has call signature of call(self, input, training)
- kwargs['training'] = False
- elif num_call_args > 2:
- # Has invalid call signature of call(self, input, *args, **kwargs)
- raise ValueError('Currently, you cannot build your model if it has '
- 'positional or keyword arguments that are not '
- 'inputs to the model, but are required for its '
- '`call` method. Instead, in order to instantiate '
- 'and build your model, `call` your model on real '
- 'tensor data with all expected call arguments.')
-
- try:
- self.call(x, **kwargs)
- except (errors.InvalidArgumentError, TypeError):
- raise ValueError('You cannot build your model by calling `build` '
- 'if your layers do not support float type inputs. '
- 'Instead, in order to instantiate and build your '
- 'model, `call` your model on real tensor data (of '
- 'the correct dtype).')
-
- if self._layers:
- self._track_layers(self._layers)
- if self.layers:
- for layer in self.layers:
- if not layer.built:
- raise ValueError('Layer: {} was not built in your model. Calling '
- '`build` manually on a subclassed model is only '
- 'allowed for models with a static topology. '
- 'In this case, you can build your model by '
- 'calling it on real tensor data.'.format(layer))
- self.built = True
-
def _check_trainable_weights_consistency(self):
"""Check trainable weights count consistency.
@@ -698,7 +529,6 @@ class Model(Network):
updates=updates,
name='train_function',
**self._function_kwargs)
- self._post_build_cleanup()
def _make_test_function(self):
if not hasattr(self, 'test_function'):
@@ -716,7 +546,6 @@ class Model(Network):
updates=self.state_updates + self.metrics_updates,
name='test_function',
**self._function_kwargs)
- self._post_build_cleanup()
def _make_predict_function(self):
if not hasattr(self, 'predict_function'):
@@ -735,7 +564,6 @@ class Model(Network):
updates=self.state_updates,
name='predict_function',
**kwargs)
- self._post_build_cleanup()
def _get_iterator_get_next_tensors(self, iterator):
get_next_op = self._iterator_get_next.get(iterator, None)
@@ -968,13 +796,7 @@ class Model(Network):
exception_prefix='input')
if y is not None:
- if context.executing_eagerly():
- feed_output_names = self.output_names
- feed_output_shapes = None
- # Sample weighting not supported in this case.
- # TODO(fchollet): consider supporting it.
- feed_sample_weight_modes = [None for _ in self.outputs]
- elif not self._is_graph_network:
+ if not self._is_graph_network:
feed_output_names = self._feed_output_names
feed_output_shapes = None
# Sample weighting not supported in this case.
diff --git a/tensorflow/python/keras/engine/training_arrays.py b/tensorflow/python/keras/engine/training_arrays.py
index adefffab11..6572e2c344 100644
--- a/tensorflow/python/keras/engine/training_arrays.py
+++ b/tensorflow/python/keras/engine/training_arrays.py
@@ -50,7 +50,6 @@ def fit_loop(model,
val_targets=None,
val_sample_weights=None,
shuffle=True,
- callback_metrics=None,
initial_epoch=0,
steps_per_epoch=None,
validation_steps=None):
@@ -69,8 +68,6 @@ def fit_loop(model,
val_targets: List of target arrays.
val_sample_weights: Optional list of sample weight arrays.
shuffle: Whether to shuffle the data at the beginning of each epoch
- callback_metrics: List of strings, the display names of the metrics
- passed to the callbacks. They should be the
concatenation of list the display names of the outputs of
`f` and the list of display names of the outputs of `f_val`.
initial_epoch: Epoch at which to start training
@@ -121,9 +118,7 @@ def fit_loop(model,
out_labels = model.metrics_names
if do_validation:
- callback_metrics = copy.copy(out_labels) + [
- 'val_' + n for n in out_labels
- ]
+ callback_metrics = copy.copy(out_labels) + ['val_' + n for n in out_labels]
# need to create the test_function before start of the first epoch
# because TensorBoard callback on_epoch_begin adds summary to the
# list of fetches of the test_function
@@ -197,9 +192,7 @@ def fit_loop(model,
if steps_per_epoch is not None:
# Step-wise fit loop.
for step_index in range(steps_per_epoch):
- batch_logs = {}
- batch_logs['batch'] = step_index
- batch_logs['size'] = 1
+ batch_logs = {'batch': step_index, 'size': 1}
callbacks.on_batch_begin(step_index, batch_logs)
try:
outs = f(ins)
@@ -388,7 +381,9 @@ def predict_loop(model, inputs, batch_size=32, verbose=0, steps=None):
return outs
-def test_loop(model, inputs, targets,
+def test_loop(model,
+ inputs,
+ targets,
sample_weights=None,
batch_size=None,
verbose=0,
@@ -485,8 +480,7 @@ def test_loop(model, inputs, targets,
if isinstance(batch_outs, list):
if batch_index == 0:
- for batch_out in enumerate(batch_outs):
- outs.append(0.)
+ outs.extend([0.] * len(batch_outs))
for i, batch_out in enumerate(batch_outs):
if i in stateful_metric_indices:
outs[i] = batch_out
diff --git a/tensorflow/python/keras/engine/training_eager.py b/tensorflow/python/keras/engine/training_eager.py
index 397de42985..c7fcf34981 100644
--- a/tensorflow/python/keras/engine/training_eager.py
+++ b/tensorflow/python/keras/engine/training_eager.py
@@ -30,35 +30,11 @@ from tensorflow.python.framework import ops
from tensorflow.python.framework import tensor_util
from tensorflow.python.keras import backend
from tensorflow.python.keras import callbacks as cbks
-from tensorflow.python.keras import losses
-from tensorflow.python.keras import metrics as metrics_module
from tensorflow.python.keras.engine import training_utils
from tensorflow.python.keras.utils import generic_utils
from tensorflow.python.platform import tf_logging as logging
-def _get_metrics_info(metric, internal_output_shapes=None, loss_func=None):
- if metric == 'accuracy' or metric == 'acc':
- # custom handling of accuracy
- # (because of class mode duality)
- output_shape = internal_output_shapes
- if output_shape[-1] == 1 or loss_func == losses.binary_crossentropy:
- # case: binary accuracy
- acc_fn = metrics_module.binary_accuracy
- elif loss_func == losses.sparse_categorical_crossentropy:
- # case: categorical accuracy with sparse targets
- acc_fn = metrics_module.sparse_categorical_accuracy
- else:
- acc_fn = metrics_module.categorical_accuracy
-
- metric_name = 'acc'
- return metric_name, acc_fn
- else:
- metric_fn = metrics_module.get(metric)
- metric_name = metric_fn.__name__
- return metric_name, metric_fn
-
-
def _eager_loss_fn(outputs, targets, loss_fn, output_name):
with backend.name_scope(output_name + '_loss'):
loss = loss_fn(targets, outputs)
@@ -74,9 +50,8 @@ def _eager_metrics_fn(model, outputs, targets):
targets: The predictions or targets of the given model.
Returns:
- Returns the metric names and metric results for each output of the model.
+ Returns the metric results for each output of the model.
"""
- metric_names = []
metric_results = []
if not isinstance(outputs, list):
outputs = [outputs]
@@ -87,18 +62,15 @@ def _eager_metrics_fn(model, outputs, targets):
for i in range(len(model.outputs)):
output_metrics = model.nested_metrics[i]
for nested_output_metric in output_metrics:
- metric_name, metric_fn = _get_metrics_info(
+ metric_fn = training_utils.get_metric_function(
nested_output_metric, backend.int_shape(model.outputs[i]),
model.loss_functions[i])
-
- if len(model.output_names) > 1:
- metric_name = model.output_names[i] + '_' + metric_name
- if metric_name not in model.metrics_names:
- model.metrics_names.append(metric_name)
+ # weighted metrics are not supported in eager mode
+ metric_name = training_utils.get_metric_name(
+ nested_output_metric, weighted=False)
with backend.name_scope(metric_name):
metric_result = metric_fn(targets[i], outputs[i])
- metric_names.append(metric_name)
metric_results.append(backend.mean(metric_result))
return metric_results
@@ -373,9 +345,16 @@ def iterator_test_loop(model, inputs, steps, verbose=0):
x, y, sample_weights = next_element
# Validate and standardize data.
- x, y, sample_weights = model._standardize_user_data(x, y)
+ x, y, sample_weights = model._standardize_user_data(
+ x, y, sample_weight=sample_weights)
x = training_utils.cast_if_floating_dtype(x)
y = training_utils.cast_if_floating_dtype(y)
+ if sample_weights:
+ sample_weights = [
+ training_utils.cast_if_floating_dtype(
+ ops.convert_to_tensor(val, dtype=backend.floatx()))
+ if val is not None else None for val in sample_weights
+ ]
# Calculate model output, loss values.
loss_outs, loss, loss_metrics = _model_loss(
@@ -617,7 +596,6 @@ def fit_loop(model,
verbose=1,
callbacks=None,
shuffle=True,
- callback_metrics=None,
initial_epoch=0,
steps_per_epoch=None,
validation_steps=None):
@@ -639,10 +617,6 @@ def fit_loop(model,
verbose: Verbosity mode, 0, 1 or 2
callbacks: List of callbacks to be called during training
shuffle: Whether to shuffle the data at the beginning of each epoch
- callback_metrics: List of strings, the display names of the metrics
- passed to the callbacks. They should be the
- concatenation of list the display names of the outputs of
- `f` and the list of display names of the outputs of `f_val`.
initial_epoch: Epoch at which to start training
(useful for resuming a previous training run)
steps_per_epoch: Total number of steps (batches of samples)
@@ -674,6 +648,7 @@ def fit_loop(model,
num_train_samples = None
out_labels = None
+ callback_metrics = None
if model._is_compiled:
out_labels = model.metrics_names
if do_validation:
diff --git a/tensorflow/python/keras/engine/training_eager_test.py b/tensorflow/python/keras/engine/training_eager_test.py
index bdb3035129..605c1935a5 100644
--- a/tensorflow/python/keras/engine/training_eager_test.py
+++ b/tensorflow/python/keras/engine/training_eager_test.py
@@ -24,291 +24,12 @@ from tensorflow.python.data.ops import dataset_ops
from tensorflow.python import keras
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util as tf_test_util
-from tensorflow.python.keras import testing_utils
from tensorflow.python.platform import test
from tensorflow.python.training.rmsprop import RMSPropOptimizer
class TrainingTest(test.TestCase):
- def test_fit_on_arrays(self):
- a = keras.layers.Input(shape=(3,), name='input_a')
- b = keras.layers.Input(shape=(3,), name='input_b')
-
- dense = keras.layers.Dense(4, name='dense')
- c = dense(a)
- d = dense(b)
- e = keras.layers.Dropout(0.5, name='dropout')(c)
-
- model = keras.models.Model([a, b], [d, e])
-
- optimizer = RMSPropOptimizer(learning_rate=0.001)
- loss = 'mse'
- loss_weights = [1., 0.5]
- metrics = ['mae']
- model.compile(optimizer, loss, metrics=metrics, loss_weights=loss_weights)
-
- input_a_np = np.random.random((10, 3))
- input_b_np = np.random.random((10, 3))
-
- output_d_np = np.random.random((10, 4))
- output_e_np = np.random.random((10, 4))
-
- # Test fit at different verbosity
- model.fit(
- [input_a_np, input_b_np], [output_d_np, output_e_np],
- epochs=1,
- batch_size=5,
- verbose=0)
- model.fit(
- [input_a_np, input_b_np], [output_d_np, output_e_np],
- epochs=1,
- batch_size=5,
- verbose=1)
- model.fit(
- [input_a_np, input_b_np], [output_d_np, output_e_np],
- epochs=2,
- batch_size=5,
- verbose=2)
-
- # Test with validation data
- model.fit(
- [input_a_np, input_b_np], [output_d_np, output_e_np],
- validation_data=([input_a_np, input_b_np], [output_d_np,
- output_e_np]),
- epochs=1,
- batch_size=5,
- verbose=0)
- model.fit(
- [input_a_np, input_b_np], [output_d_np, output_e_np],
- validation_data=([input_a_np, input_b_np], [output_d_np,
- output_e_np]),
- epochs=2,
- batch_size=5,
- verbose=1)
- model.fit(
- [input_a_np, input_b_np], [output_d_np, output_e_np],
- validation_data=([input_a_np, input_b_np], [output_d_np,
- output_e_np]),
- epochs=2,
- batch_size=5,
- verbose=2)
- model.train_on_batch([input_a_np, input_b_np], [output_d_np, output_e_np])
-
- # Test with validation split
- model.fit(
- [input_a_np, input_b_np], [output_d_np, output_e_np],
- epochs=2,
- batch_size=5,
- verbose=0,
- validation_split=0.2)
-
- # Test with dictionary inputs
- model.fit(
- {
- 'input_a': input_a_np,
- 'input_b': input_b_np
- }, {'dense': output_d_np,
- 'dropout': output_e_np},
- epochs=1,
- batch_size=5,
- verbose=0)
- model.fit(
- {
- 'input_a': input_a_np,
- 'input_b': input_b_np
- }, {'dense': output_d_np,
- 'dropout': output_e_np},
- epochs=1,
- batch_size=5,
- verbose=1)
- model.fit(
- {
- 'input_a': input_a_np,
- 'input_b': input_b_np
- }, {'dense': output_d_np,
- 'dropout': output_e_np},
- validation_data=({'input_a': input_a_np,
- 'input_b': input_b_np
- },
- {
- 'dense': output_d_np,
- 'dropout': output_e_np
- }),
- epochs=1,
- batch_size=5,
- verbose=0)
- model.train_on_batch({
- 'input_a': input_a_np,
- 'input_b': input_b_np
- }, {'dense': output_d_np,
- 'dropout': output_e_np})
- # Test with lists for loss, metrics
- loss = ['mae', 'mse']
- metrics = ['acc', 'mae']
- model.compile(optimizer, loss, metrics=metrics)
- model.fit(
- [input_a_np, input_b_np], [output_d_np, output_e_np],
- epochs=1,
- batch_size=5,
- verbose=0)
-
- # Test with dictionaries for loss, metrics, loss weights
- loss = {'dense': 'mse', 'dropout': 'mae'}
- loss_weights = {'dense': 1., 'dropout': 0.5}
- metrics = {'dense': 'mse', 'dropout': 'mae'}
- model.compile(optimizer, loss, metrics=metrics, loss_weights=loss_weights)
- model.fit(
- [input_a_np, input_b_np], [output_d_np, output_e_np],
- epochs=1,
- batch_size=5,
- verbose=0)
-
- # Invalid use cases
- with self.assertRaises(AttributeError):
- model.fit(
- [input_a_np, input_b_np], [output_d_np, output_e_np],
- epochs=1,
- validation_data=([input_a_np, input_b_np], 0, 0),
- verbose=0)
- with self.assertRaises(ValueError):
- model.train_on_batch({'input_a': input_a_np},
- [output_d_np, output_e_np])
- with self.assertRaises(ValueError):
- model.train_on_batch([input_a_np], [output_d_np, output_e_np])
- with self.assertRaises(AttributeError):
- model.train_on_batch(1, [output_d_np, output_e_np])
- with self.assertRaises(ValueError):
- model.train_on_batch(input_a_np, [output_d_np, output_e_np])
- with self.assertRaises(ValueError):
- bad_input = np.random.random((11, 3))
- model.train_on_batch([bad_input, input_b_np],
- [output_d_np, output_e_np])
- with self.assertRaises(ValueError):
- bad_target = np.random.random((11, 4))
- model.train_on_batch([input_a_np, input_b_np],
- [bad_target, output_e_np])
-
- # Build single-input model
- x = keras.layers.Input(shape=(3,), name='input_a')
- y = keras.layers.Dense(4)(x)
- model = keras.models.Model(x, y)
- model.compile(optimizer=RMSPropOptimizer(learning_rate=0.001), loss='mse')
- # This will work
- model.fit([input_a_np], output_d_np, epochs=1)
- with self.assertRaises(ValueError):
- model.fit([input_a_np, input_a_np], output_d_np, epochs=1)
-
- def test_evaluate_predict_on_arrays(self):
- a = keras.layers.Input(shape=(3,), name='input_a')
- b = keras.layers.Input(shape=(3,), name='input_b')
-
- dense = keras.layers.Dense(4, name='dense')
- c = dense(a)
- d = dense(b)
- e = keras.layers.Dropout(0.5, name='dropout')(c)
-
- model = keras.models.Model([a, b], [d, e])
-
- optimizer = RMSPropOptimizer(learning_rate=0.001)
- loss = 'mse'
- loss_weights = [1., 0.5]
- metrics = ['acc', 'mae']
- model.compile(
- optimizer,
- loss,
- metrics=metrics,
- loss_weights=loss_weights,
- sample_weight_mode=None)
-
- input_a_np = np.random.random((10, 3))
- input_b_np = np.random.random((10, 3))
-
- output_d_np = np.random.random((10, 4))
- output_e_np = np.random.random((10, 4))
-
- # Test evaluate at different verbosity
- out = model.evaluate(
- [input_a_np, input_b_np], [output_d_np, output_e_np],
- batch_size=5,
- verbose=0)
- self.assertEqual(len(out), 7)
- out = model.evaluate(
- [input_a_np, input_b_np], [output_d_np, output_e_np],
- batch_size=5,
- verbose=1)
- self.assertEqual(len(out), 7)
- out = model.evaluate(
- [input_a_np, input_b_np], [output_d_np, output_e_np],
- batch_size=5,
- verbose=2)
- self.assertEqual(len(out), 7)
- out = model.test_on_batch([input_a_np, input_b_np],
- [output_d_np, output_e_np])
- self.assertEqual(len(out), 7)
-
- # Test evaluate with dictionary inputs
- model.evaluate(
- {
- 'input_a': input_a_np,
- 'input_b': input_b_np
- }, {'dense': output_d_np,
- 'dropout': output_e_np},
- batch_size=5,
- verbose=0)
- model.evaluate(
- {
- 'input_a': input_a_np,
- 'input_b': input_b_np
- }, {'dense': output_d_np,
- 'dropout': output_e_np},
- batch_size=5,
- verbose=1)
-
- # Test predict
- out = model.predict([input_a_np, input_b_np], batch_size=5)
- self.assertEqual(len(out), 2)
- out = model.predict({'input_a': input_a_np, 'input_b': input_b_np})
- self.assertEqual(len(out), 2)
- out = model.predict_on_batch({
- 'input_a': input_a_np,
- 'input_b': input_b_np
- })
- self.assertEqual(len(out), 2)
-
- def test_invalid_loss_or_metrics(self):
- num_classes = 5
- train_samples = 1000
- test_samples = 1000
- input_dim = 5
-
- model = keras.models.Sequential()
- model.add(keras.layers.Dense(10, input_shape=(input_dim,)))
- model.add(keras.layers.Activation('relu'))
- model.add(keras.layers.Dense(num_classes))
- model.add(keras.layers.Activation('softmax'))
- model.compile(loss='categorical_crossentropy',
- optimizer=RMSPropOptimizer(learning_rate=0.001))
- np.random.seed(1337)
-
- (x_train, y_train), (_, _) = testing_utils.get_test_data(
- train_samples=train_samples,
- test_samples=test_samples,
- input_shape=(input_dim,),
- num_classes=num_classes)
-
- with self.assertRaises(ValueError):
- model.fit(x_train, np.concatenate([y_train, y_train], axis=-1))
-
- with self.assertRaises(TypeError):
- model.compile(loss='categorical_crossentropy',
- optimizer=RMSPropOptimizer(learning_rate=0.001),
- metrics=set(0))
-
- with self.assertRaises(ValueError):
- model.compile(loss=None,
- optimizer='rms')
-
def test_model_methods_with_eager_tensors_multi_io(self):
a = keras.layers.Input(shape=(3,), name='input_a')
b = keras.layers.Input(shape=(3,), name='input_b')
@@ -422,229 +143,6 @@ class TrainingTest(test.TestCase):
self.assertEqual(out.shape, (30, 4))
-class LossWeightingTest(test.TestCase):
-
- def test_class_weights(self):
- num_classes = 5
- batch_size = 5
- weighted_class = 3
- train_samples = 300
- test_samples = 300
- input_dim = 5
-
- model = keras.models.Sequential()
- model.add(keras.layers.Dense(10, input_shape=(input_dim,)))
- model.add(keras.layers.Activation('relu'))
- model.add(keras.layers.Dense(num_classes))
- model.add(keras.layers.Activation('softmax'))
- model.compile(loss='categorical_crossentropy',
- optimizer=RMSPropOptimizer(learning_rate=0.001))
-
- np.random.seed(1337)
- (x_train, y_train), (x_test, y_test) = testing_utils.get_test_data(
- train_samples=train_samples,
- test_samples=test_samples,
- input_shape=(input_dim,),
- num_classes=num_classes)
- int_y_test = y_test.copy()
- int_y_train = y_train.copy()
- # convert class vectors to binary class matrices
- y_train = keras.utils.to_categorical(y_train, num_classes)
- y_test = keras.utils.to_categorical(y_test, num_classes)
- test_ids = np.where(int_y_test == np.array(weighted_class))[0]
-
- class_weight = dict([(i, 1.) for i in range(num_classes)])
- class_weight[weighted_class] = 4.
-
- sample_weight = np.ones((y_train.shape[0]))
- sample_weight[int_y_train == weighted_class] = 4.
-
- model.fit(
- x_train,
- y_train,
- batch_size=batch_size,
- epochs=2,
- verbose=0,
- class_weight=class_weight,
- validation_data=(x_train, y_train, sample_weight))
- model.fit(
- x_train,
- y_train,
- batch_size=batch_size,
- epochs=2,
- verbose=0,
- class_weight=class_weight)
- model.fit(
- x_train,
- y_train,
- batch_size=batch_size,
- epochs=2,
- verbose=0,
- class_weight=class_weight,
- validation_split=0.1)
-
- model.train_on_batch(
- x_train[:batch_size], y_train[:batch_size], class_weight=class_weight)
- ref_score = model.evaluate(x_test, y_test, verbose=0)
- score = model.evaluate(
- x_test[test_ids, :], y_test[test_ids, :], verbose=0)
- self.assertLess(score, ref_score)
-
- def test_sample_weights(self):
- num_classes = 5
- batch_size = 5
- weighted_class = 3
- train_samples = 300
- test_samples = 300
- input_dim = 5
-
- model = keras.models.Sequential()
- model.add(keras.layers.Dense(10, input_shape=(input_dim,)))
- model.add(keras.layers.Activation('relu'))
- model.add(keras.layers.Dense(num_classes))
- model.add(keras.layers.Activation('softmax'))
- model.compile(loss='categorical_crossentropy',
- optimizer=RMSPropOptimizer(learning_rate=0.001))
-
- np.random.seed(43)
- (x_train, y_train), _ = testing_utils.get_test_data(
- train_samples=train_samples,
- test_samples=test_samples,
- input_shape=(input_dim,),
- num_classes=num_classes)
- int_y_train = y_train.copy()
- y_train = keras.utils.to_categorical(y_train, num_classes)
-
- class_weight = dict([(i, 1.) for i in range(num_classes)])
- class_weight[weighted_class] = 4.
-
- sample_weight = np.ones((y_train.shape[0]))
- sample_weight[int_y_train == weighted_class] = 4.
-
- model.fit(
- x_train,
- y_train,
- batch_size=batch_size,
- epochs=2,
- verbose=0,
- sample_weight=sample_weight)
- model.fit(
- x_train,
- y_train,
- batch_size=batch_size,
- epochs=2,
- verbose=0,
- sample_weight=sample_weight,
- validation_split=0.1)
- model.train_on_batch(
- x_train[:batch_size],
- y_train[:batch_size],
- sample_weight=sample_weight[:batch_size])
- model.test_on_batch(
- x_train[:batch_size],
- y_train[:batch_size],
- sample_weight=sample_weight[:batch_size])
-
- def test_temporal_sample_weights(self):
- num_classes = 5
- weighted_class = 3
- train_samples = 1000
- test_samples = 1000
- input_dim = 5
- timesteps = 3
-
- model = keras.models.Sequential()
- model.add(
- keras.layers.TimeDistributed(
- keras.layers.Dense(num_classes),
- input_shape=(timesteps, input_dim)))
- model.add(keras.layers.Activation('softmax'))
-
- np.random.seed(1337)
- (_, y_train), _ = testing_utils.get_test_data(
- train_samples=train_samples,
- test_samples=test_samples,
- input_shape=(input_dim,),
- num_classes=num_classes)
- int_y_train = y_train.copy()
- # convert class vectors to binary class matrices
- y_train = keras.utils.to_categorical(y_train, num_classes)
-
- class_weight = dict([(i, 1.) for i in range(num_classes)])
- class_weight[weighted_class] = 2.
-
- sample_weight = np.ones((y_train.shape[0]))
- sample_weight[int_y_train == weighted_class] = 2.
- with self.assertRaises(ValueError):
- model.compile(
- loss='binary_crossentropy',
- optimizer=RMSPropOptimizer(learning_rate=0.001),
- sample_weight_mode='temporal')
-
- def test_class_weight_invalid_use_case(self):
- num_classes = 5
- train_samples = 1000
- test_samples = 1000
- input_dim = 5
- timesteps = 3
-
- model = keras.models.Sequential()
- model.add(
- keras.layers.TimeDistributed(
- keras.layers.Dense(num_classes),
- input_shape=(timesteps, input_dim)))
- model.add(keras.layers.Activation('softmax'))
- model.compile(
- loss='binary_crossentropy',
- optimizer=RMSPropOptimizer(learning_rate=0.001))
-
- (x_train, y_train), _ = testing_utils.get_test_data(
- train_samples=train_samples,
- test_samples=test_samples,
- input_shape=(input_dim,),
- num_classes=num_classes)
- # convert class vectors to binary class matrices
- y_train = keras.utils.to_categorical(y_train, num_classes)
- class_weight = dict([(i, 1.) for i in range(num_classes)])
-
- del class_weight[1]
- with self.assertRaises(ValueError):
- model.fit(x_train, y_train,
- epochs=0, verbose=0, class_weight=class_weight)
-
- with self.assertRaises(ValueError):
- model.compile(
- loss='binary_crossentropy',
- optimizer=RMSPropOptimizer(learning_rate=0.001),
- sample_weight_mode=[])
-
- # Build multi-output model
- x = keras.Input((3,))
- y1 = keras.layers.Dense(4, name='1')(x)
- y2 = keras.layers.Dense(4, name='2')(x)
- model = keras.models.Model(x, [y1, y2])
- model.compile(optimizer=RMSPropOptimizer(learning_rate=0.001), loss='mse')
- x_np = np.random.random((10, 3))
- y_np = np.random.random((10, 4))
- w_np = np.random.random((10,))
- # This will work
- model.fit(x_np, [y_np, y_np], epochs=1, sample_weight={'1': w_np})
- # These will not
- with self.assertRaises(ValueError):
- model.fit(x_np, [y_np, y_np], epochs=1, sample_weight=[w_np])
- with self.assertRaises(TypeError):
- model.fit(x_np, [y_np, y_np], epochs=1, sample_weight=w_np)
- with self.assertRaises(ValueError):
- bad_w_np = np.random.random((11,))
- model.fit(x_np, [y_np, y_np], epochs=1, sample_weight={'1': bad_w_np})
- with self.assertRaises(ValueError):
- bad_w_np = np.random.random((10, 2))
- model.fit(x_np, [y_np, y_np], epochs=1, sample_weight={'1': bad_w_np})
- with self.assertRaises(ValueError):
- bad_w_np = np.random.random((10, 2, 2))
- model.fit(x_np, [y_np, y_np], epochs=1, sample_weight={'1': bad_w_np})
-
-
class CorrectnessTest(test.TestCase):
@tf_test_util.run_in_graph_and_eager_modes
diff --git a/tensorflow/python/keras/engine/training_test.py b/tensorflow/python/keras/engine/training_test.py
index 301a6ca866..e89847dbbf 100644
--- a/tensorflow/python/keras/engine/training_test.py
+++ b/tensorflow/python/keras/engine/training_test.py
@@ -26,6 +26,7 @@ import numpy as np
from tensorflow.python import keras
from tensorflow.python.data.ops import dataset_ops
+from tensorflow.python.eager import context
from tensorflow.python.framework import ops
from tensorflow.python.framework import tensor_shape
from tensorflow.python.framework import test_util as tf_test_util
@@ -45,6 +46,7 @@ except ImportError:
class TrainingTest(test.TestCase):
+ @tf_test_util.run_in_graph_and_eager_modes
def test_fit_on_arrays(self):
with self.test_session():
a = keras.layers.Input(shape=(3,), name='input_a')
@@ -57,7 +59,7 @@ class TrainingTest(test.TestCase):
model = keras.models.Model([a, b], [d, e])
- optimizer = 'rmsprop'
+ optimizer = RMSPropOptimizer(learning_rate=0.001)
loss = 'mse'
loss_weights = [1., 0.5]
metrics = ['mae']
@@ -224,7 +226,7 @@ class TrainingTest(test.TestCase):
x = keras.layers.Input(shape=(3,), name='input_a')
y = keras.layers.Dense(4)(x)
model = keras.models.Model(x, y)
- model.compile(optimizer='rmsprop', loss='mse')
+ model.compile(optimizer, loss='mse')
# This will work
model.fit([input_a_np], output_d_np, epochs=1)
with self.assertRaises(ValueError):
@@ -240,6 +242,7 @@ class TrainingTest(test.TestCase):
batch_size=5,
verbose=2)
+ @tf_test_util.run_in_graph_and_eager_modes
def test_evaluate_predict_on_arrays(self):
with self.test_session():
a = keras.layers.Input(shape=(3,), name='input_a')
@@ -252,7 +255,7 @@ class TrainingTest(test.TestCase):
model = keras.models.Model([a, b], [d, e])
- optimizer = 'rmsprop'
+ optimizer = RMSPropOptimizer(learning_rate=0.001)
loss = 'mse'
loss_weights = [1., 0.5]
metrics = ['mae']
@@ -322,6 +325,7 @@ class TrainingTest(test.TestCase):
})
self.assertEqual(len(out), 2)
+ @tf_test_util.run_in_graph_and_eager_modes
def test_invalid_loss_or_metrics(self):
num_classes = 5
train_samples = 1000
@@ -334,27 +338,29 @@ class TrainingTest(test.TestCase):
model.add(keras.layers.Activation('relu'))
model.add(keras.layers.Dense(num_classes))
model.add(keras.layers.Activation('softmax'))
- model.compile(loss='categorical_crossentropy', optimizer='rmsprop')
+ optimizer = RMSPropOptimizer(learning_rate=0.001)
+ model.compile(optimizer, loss='categorical_crossentropy')
np.random.seed(1337)
(x_train, y_train), (_, _) = testing_utils.get_test_data(
train_samples=train_samples,
test_samples=test_samples,
input_shape=(input_dim,),
num_classes=num_classes)
- with self.assertRaises(ValueError):
- model.fit(x_train, y_train)
with self.assertRaises(ValueError):
model.fit(x_train, np.concatenate([y_train, y_train], axis=-1))
with self.assertRaises(TypeError):
- model.compile(loss='categorical_crossentropy',
- optimizer='rmsprop',
- metrics=set(0))
+ model.compile(
+ optimizer, loss='categorical_crossentropy', metrics=set(0))
- with self.assertRaises(ValueError):
- model.compile(loss=None,
- optimizer='rmsprop')
+ if not context.executing_eagerly():
+ # TODO(psv): Investigate these use cases in eager mode.
+ with self.assertRaises(ValueError):
+ model.fit(x_train, y_train)
+
+ with self.assertRaises(ValueError):
+ model.compile(optimizer, loss=None)
def test_training_on_sparse_data_with_dense_placeholders(self):
if scipy_sparse is None:
@@ -441,6 +447,7 @@ class TrainingTest(test.TestCase):
class LossWeightingTest(test.TestCase):
+ @tf_test_util.run_in_graph_and_eager_modes
def test_class_weights(self):
num_classes = 5
batch_size = 5
@@ -449,6 +456,7 @@ class LossWeightingTest(test.TestCase):
train_samples = 1000
test_samples = 1000
input_dim = 5
+ learning_rate = 0.001
with self.test_session():
model = keras.models.Sequential()
@@ -456,7 +464,9 @@ class LossWeightingTest(test.TestCase):
model.add(keras.layers.Activation('relu'))
model.add(keras.layers.Dense(num_classes))
model.add(keras.layers.Activation('softmax'))
- model.compile(loss='categorical_crossentropy', optimizer='rmsprop')
+ model.compile(
+ loss='categorical_crossentropy',
+ optimizer=RMSPropOptimizer(learning_rate=learning_rate))
np.random.seed(1337)
(x_train, y_train), (x_test, y_test) = testing_utils.get_test_data(
@@ -508,6 +518,7 @@ class LossWeightingTest(test.TestCase):
x_test[test_ids, :], y_test[test_ids, :], verbose=0)
self.assertLess(score, ref_score)
+ @tf_test_util.run_in_graph_and_eager_modes
def test_sample_weights(self):
num_classes = 5
batch_size = 5
@@ -516,6 +527,7 @@ class LossWeightingTest(test.TestCase):
train_samples = 1000
test_samples = 1000
input_dim = 5
+ learning_rate = 0.001
with self.test_session():
model = keras.models.Sequential()
@@ -523,7 +535,9 @@ class LossWeightingTest(test.TestCase):
model.add(keras.layers.Activation('relu'))
model.add(keras.layers.Dense(num_classes))
model.add(keras.layers.Activation('softmax'))
- model.compile(loss='categorical_crossentropy', optimizer='rmsprop')
+ model.compile(
+ RMSPropOptimizer(learning_rate=learning_rate),
+ loss='categorical_crossentropy')
np.random.seed(43)
(x_train, y_train), (x_test, y_test) = testing_utils.get_test_data(
@@ -538,9 +552,6 @@ class LossWeightingTest(test.TestCase):
y_test = keras.utils.to_categorical(y_test, num_classes)
test_ids = np.where(int_y_test == np.array(weighted_class))[0]
- class_weight = dict([(i, 1.) for i in range(num_classes)])
- class_weight[weighted_class] = 2.
-
sample_weight = np.ones((y_train.shape[0]))
sample_weight[int_y_train == weighted_class] = 2.
@@ -569,10 +580,12 @@ class LossWeightingTest(test.TestCase):
y_train[:batch_size],
sample_weight=sample_weight[:batch_size])
ref_score = model.evaluate(x_test, y_test, verbose=0)
- score = model.evaluate(
- x_test[test_ids, :], y_test[test_ids, :], verbose=0)
- self.assertLess(score, ref_score)
+ if not context.executing_eagerly():
+ score = model.evaluate(
+ x_test[test_ids, :], y_test[test_ids, :], verbose=0)
+ self.assertLess(score, ref_score)
+ @tf_test_util.run_in_graph_and_eager_modes
def test_temporal_sample_weights(self):
num_classes = 5
batch_size = 5
@@ -582,6 +595,7 @@ class LossWeightingTest(test.TestCase):
test_samples = 1000
input_dim = 5
timesteps = 3
+ learning_rate = 0.001
with self.test_session():
model = keras.models.Sequential()
@@ -604,9 +618,6 @@ class LossWeightingTest(test.TestCase):
y_test = keras.utils.to_categorical(y_test, num_classes)
test_ids = np.where(int_y_test == np.array(weighted_class))[0]
- class_weight = dict([(i, 1.) for i in range(num_classes)])
- class_weight[weighted_class] = 2.
-
sample_weight = np.ones((y_train.shape[0]))
sample_weight[int_y_train == weighted_class] = 2.
@@ -628,8 +639,8 @@ class LossWeightingTest(test.TestCase):
temporal_sample_weight, timesteps, axis=1)
model.compile(
+ RMSPropOptimizer(learning_rate=learning_rate),
loss='binary_crossentropy',
- optimizer='rmsprop',
sample_weight_mode='temporal')
model.fit(
@@ -657,16 +668,19 @@ class LossWeightingTest(test.TestCase):
temporal_y_train[:batch_size],
sample_weight=temporal_sample_weight[:batch_size])
ref_score = model.evaluate(temporal_x_test, temporal_y_test, verbose=0)
- score = model.evaluate(
- temporal_x_test[test_ids], temporal_y_test[test_ids], verbose=0)
- self.assertLess(score, ref_score)
+ if not context.executing_eagerly():
+ score = model.evaluate(
+ temporal_x_test[test_ids], temporal_y_test[test_ids], verbose=0)
+ self.assertLess(score, ref_score)
+ @tf_test_util.run_in_graph_and_eager_modes
def test_class_weight_invalid_use_case(self):
num_classes = 5
train_samples = 1000
test_samples = 1000
input_dim = 5
timesteps = 3
+ learning_rate = 0.001
with self.test_session():
model = keras.models.Sequential()
@@ -675,9 +689,8 @@ class LossWeightingTest(test.TestCase):
keras.layers.Dense(num_classes),
input_shape=(timesteps, input_dim)))
model.add(keras.layers.Activation('softmax'))
- model.compile(
- loss='binary_crossentropy',
- optimizer='rmsprop')
+ optimizer = RMSPropOptimizer(learning_rate=learning_rate)
+ model.compile(optimizer, loss='binary_crossentropy')
(x_train, y_train), _ = testing_utils.get_test_data(
train_samples=train_samples,
@@ -695,16 +708,14 @@ class LossWeightingTest(test.TestCase):
with self.assertRaises(ValueError):
model.compile(
- loss='binary_crossentropy',
- optimizer='rmsprop',
- sample_weight_mode=[])
+ optimizer, loss='binary_crossentropy', sample_weight_mode=[])
# Build multi-output model
x = keras.Input((3,))
y1 = keras.layers.Dense(4, name='1')(x)
y2 = keras.layers.Dense(4, name='2')(x)
model = keras.models.Model(x, [y1, y2])
- model.compile(optimizer='rmsprop', loss='mse')
+ model.compile(optimizer, loss='mse')
x_np = np.random.random((10, 3))
y_np = np.random.random((10, 4))
w_np = np.random.random((10,))
@@ -731,6 +742,54 @@ class LossWeightingTest(test.TestCase):
model.fit(x_np, [y_np, y_np], epochs=1,
sample_weight={'1': bad_w_np})
+ @tf_test_util.run_in_graph_and_eager_modes
+ def test_default_sample_weight(self):
+ """Verifies that fit works without having to set sample_weight."""
+
+ num_classes = 5
+ input_dim = 5
+ timesteps = 3
+ learning_rate = 0.001
+
+ with self.test_session():
+ model = keras.models.Sequential()
+ model.add(
+ keras.layers.TimeDistributed(
+ keras.layers.Dense(num_classes),
+ input_shape=(timesteps, input_dim)))
+
+ x = np.random.random((10, timesteps, input_dim))
+ y = np.random.random((10, timesteps, num_classes))
+ optimizer = RMSPropOptimizer(learning_rate=learning_rate)
+
+ # sample_weight_mode is a list and mode value is None
+ model.compile(optimizer, loss='mse', sample_weight_mode=[None])
+ model.fit(x, y, epochs=1, batch_size=10)
+
+ # sample_weight_mode is a list and mode value is `temporal`
+ model.compile(optimizer, loss='mse', sample_weight_mode=['temporal'])
+ model.fit(x, y, epochs=1, batch_size=10)
+
+ # sample_weight_mode is a dict and mode value is None
+ model.compile(
+ optimizer, loss='mse', sample_weight_mode={'time_distributed': None})
+ model.fit(x, y, epochs=1, batch_size=10)
+
+ # sample_weight_mode is a dict and mode value is `temporal`
+ model.compile(
+ optimizer,
+ loss='mse',
+ sample_weight_mode={'time_distributed': 'temporal'})
+ model.fit(x, y, epochs=1, batch_size=10)
+
+ # sample_weight_mode is a not a list/dict and mode value is None
+ model.compile(optimizer, loss='mse', sample_weight_mode=None)
+ model.fit(x, y, epochs=1, batch_size=10)
+
+ # sample_weight_mode is a not a list/dict and mode value is `temporal`
+ model.compile(optimizer, loss='mse', sample_weight_mode='temporal')
+ model.fit(x, y, epochs=1, batch_size=10)
+
class LossMaskingTest(test.TestCase):
diff --git a/tensorflow/python/keras/engine/training_utils.py b/tensorflow/python/keras/engine/training_utils.py
index dbbc87daf9..b304cb9093 100644
--- a/tensorflow/python/keras/engine/training_utils.py
+++ b/tensorflow/python/keras/engine/training_utils.py
@@ -26,10 +26,12 @@ import numpy as np
from tensorflow.python.data.ops import dataset_ops
from tensorflow.python.data.ops import iterator_ops
from tensorflow.python.eager import context
+from tensorflow.python.framework import constant_op
from tensorflow.python.framework import tensor_util
from tensorflow.python.keras import backend as K
from tensorflow.python.keras import losses
from tensorflow.python.keras import metrics as metrics_module
+from tensorflow.python.ops import array_ops
from tensorflow.python.ops import math_ops
@@ -700,17 +702,16 @@ def populate_metric_names(model):
for i in range(len(model.outputs)):
metrics = model.nested_metrics[i]
for metric in metrics:
- base_metric_name = get_base_metric_name(metric)
+ base_metric_name = get_metric_name(metric)
add_metric_name(model, base_metric_name, i)
-def get_base_metric_name(metric, weighted=False):
- """Returns the metric name given the metric function.
+def get_metric_name(metric, weighted=False):
+ """Returns the metric name corresponding to the given metric input.
Arguments:
metric: Metric function name or reference.
- weighted: Boolean indicating if the metric for which we are adding
- names is weighted.
+ weighted: Boolean indicating if the given metric is weighted.
Returns:
a metric name.
@@ -734,6 +735,36 @@ def get_base_metric_name(metric, weighted=False):
return metric_name
+def get_metric_function(metric, output_shape=None, loss_fn=None):
+ """Returns the metric function corresponding to the given metric input.
+
+ Arguments:
+ metric: Metric function name or reference.
+ output_shape: The shape of the output that this metric
+ will be calculated for.
+ loss_fn: The loss function used.
+
+ Returns:
+ The metric function.
+ """
+ if metric in ['accuracy', 'acc']:
+ if output_shape[-1] == 1 or loss_fn == losses.binary_crossentropy:
+ return metrics_module.binary_accuracy # case: binary accuracy
+ elif loss_fn == losses.sparse_categorical_crossentropy:
+ # case: categorical accuracy with sparse targets
+ return metrics_module.sparse_categorical_accuracy
+ return metrics_module.categorical_accuracy # case: categorical accuracy
+ elif metric in ['crossentropy', 'ce']:
+ if output_shape[-1] == 1 or loss_fn == losses.binary_crossentropy:
+ return metrics_module.binary_crossentropy # case: binary cross-entropy
+ elif loss_fn == losses.sparse_categorical_crossentropy:
+ # case: categorical cross-entropy with sparse targets
+ return metrics_module.sparse_categorical_crossentropy
+ # case: categorical cross-entropy
+ return metrics_module.categorical_crossentropy
+ return metrics_module.get(metric)
+
+
def add_metric_name(model, metric_name, index):
"""Makes the metric name unique and adds it to the model's metric name list.
@@ -746,6 +777,9 @@ def add_metric_name(model, metric_name, index):
user. For example: 'acc'
index: The index of the model output for which the metric name is being
added.
+
+ Returns:
+ string, name of the model's unique metric name
"""
if len(model.output_names) > 1:
metric_name = '%s_%s' % (model.output_names[index], metric_name)
@@ -755,6 +789,7 @@ def add_metric_name(model, metric_name, index):
metric_name = '%s_%d' % (base_metric_name, j)
j += 1
model.metrics_names.append(metric_name)
+ return metric_name
def validate_iterator_input(x, y, sample_weight, validation_split=None):
@@ -856,3 +891,83 @@ def cast_if_floating_dtype(x):
for val in x
]
return math_ops.cast(x, dtype=K.floatx()) if x.dtype.is_floating else x
+
+
+def get_output_sample_weight_and_mode(skip_target_weighing_indices,
+ sample_weight_mode, output_name,
+ output_index):
+ """Returns the sample weight and weight mode for a single output."""
+ if output_index in skip_target_weighing_indices:
+ return None, None
+
+ if sample_weight_mode == 'temporal':
+ default_value = [[1.]]
+ shape = [None, None]
+ mode = 'temporal'
+ else:
+ default_value = [1.]
+ shape = [None]
+ mode = None
+ if context.executing_eagerly():
+ weight = None
+ else:
+ weight = array_ops.placeholder_with_default(
+ constant_op.constant(default_value, dtype=K.floatx()),
+ shape=shape,
+ name=output_name + '_sample_weights')
+ return weight, mode
+
+
+def prepare_sample_weights(output_names, sample_weight_mode,
+ skip_target_weighing_indices):
+ """Prepares sample weights for the model.
+
+ Args:
+ output_names: List of model output names.
+ sample_weight_mode: sample weight mode user input passed from compile API.
+ skip_target_weighing_indices: Indices of output for which sample weights
+ should be skipped.
+
+ Returns:
+ A pair of list of sample weights and sample weight modes
+ (one for each output).
+
+ Raises:
+ ValueError: In case of invalid `sample_weight_mode` input.
+ """
+ sample_weights = []
+ sample_weight_modes = []
+ if isinstance(sample_weight_mode, dict):
+ unknown_output = set(sample_weight_mode.keys()) - set(output_names)
+ if unknown_output:
+ raise ValueError('Unknown entry in '
+ 'sample_weight_mode dictionary: "' + unknown_output +
+ '". Only expected the following keys: ' +
+ str(output_names))
+ for i, name in enumerate(output_names):
+ if (i not in skip_target_weighing_indices and
+ name not in sample_weight_mode):
+ raise ValueError('Output missing from sample_weight_modes dictionary')
+ weight, mode = get_output_sample_weight_and_mode(
+ skip_target_weighing_indices, sample_weight_mode.get(name), name, i)
+ sample_weights.append(weight)
+ sample_weight_modes.append(mode)
+ elif isinstance(sample_weight_mode, list):
+ if len(sample_weight_mode) != len(output_names):
+ raise ValueError('When passing a list as sample_weight_mode, '
+ 'it should have one entry per model output. '
+ 'The model has ' + str(len(output_names)) +
+ ' outputs, but you passed ' +
+ str(len(sample_weight_mode)) + 'sample_weight_modes')
+ for i, name in enumerate(output_names):
+ weight, mode = get_output_sample_weight_and_mode(
+ skip_target_weighing_indices, sample_weight_mode[i], name, i)
+ sample_weights.append(weight)
+ sample_weight_modes.append(mode)
+ else:
+ for i, name in enumerate(output_names):
+ weight, mode = get_output_sample_weight_and_mode(
+ skip_target_weighing_indices, sample_weight_mode, name, i)
+ sample_weights.append(weight)
+ sample_weight_modes.append(mode)
+ return sample_weights, sample_weight_modes
diff --git a/tensorflow/python/keras/model_subclassing_test.py b/tensorflow/python/keras/model_subclassing_test.py
index 5fbc191e78..3a153573f8 100644
--- a/tensorflow/python/keras/model_subclassing_test.py
+++ b/tensorflow/python/keras/model_subclassing_test.py
@@ -180,9 +180,6 @@ def get_nested_model_3(input_dim, num_classes):
x = self.dense2(x)
return self.bn(x)
- def compute_output_shape(self, input_shape):
- return tensor_shape.TensorShape((input_shape[0], 5))
-
test_model = Inner()
x = test_model(x)
outputs = keras.layers.Dense(num_classes)(x)
diff --git a/tensorflow/python/kernel_tests/BUILD b/tensorflow/python/kernel_tests/BUILD
index adf97569ab..edb0910354 100644
--- a/tensorflow/python/kernel_tests/BUILD
+++ b/tensorflow/python/kernel_tests/BUILD
@@ -1718,7 +1718,7 @@ cuda_py_test(
cuda_py_test(
name = "matmul_op_test",
- size = "small",
+ size = "medium",
srcs = ["matmul_op_test.py"],
additional_deps = [
"//third_party/py/numpy",
diff --git a/tensorflow/python/kernel_tests/control_flow_ops_py_test.py b/tensorflow/python/kernel_tests/control_flow_ops_py_test.py
index 68873df97e..b567b71424 100644
--- a/tensorflow/python/kernel_tests/control_flow_ops_py_test.py
+++ b/tensorflow/python/kernel_tests/control_flow_ops_py_test.py
@@ -734,11 +734,11 @@ class ControlFlowTest(test.TestCase):
def body_fn(i):
with ops.control_dependencies([increment]):
- return i + i
+ return i + 1
- result = control_flow_ops.while_loop(cond=lambda i: i < 1,
+ result = control_flow_ops.while_loop(cond=lambda i: i < 2,
body=body_fn, loop_vars=[1])
- result.eval()
+ self.assertAllEqual(result.eval(), 2)
self.assertAllEqual(v.eval(), 1.0)
def testWhileExternalControlDependenciesNoInput(self):
diff --git a/tensorflow/python/kernel_tests/conv_ops_test.py b/tensorflow/python/kernel_tests/conv_ops_test.py
index 474d06b8f3..00de94f004 100644
--- a/tensorflow/python/kernel_tests/conv_ops_test.py
+++ b/tensorflow/python/kernel_tests/conv_ops_test.py
@@ -1706,7 +1706,7 @@ class SeparableConv2DTest(test.TestCase):
def testSeparableConv2D(self):
self._testSeparableConv2D("NHWC")
- def testSeparableConv2DNCHW(self):
+ def disabledtestSeparableConv2DNCHW(self):
if not test.is_gpu_available():
return
self._testSeparableConv2D("NCHW")
diff --git a/tensorflow/python/kernel_tests/decode_jpeg_op_test.py b/tensorflow/python/kernel_tests/decode_jpeg_op_test.py
index 510daf79dc..66b3e0f22f 100644
--- a/tensorflow/python/kernel_tests/decode_jpeg_op_test.py
+++ b/tensorflow/python/kernel_tests/decode_jpeg_op_test.py
@@ -110,7 +110,8 @@ class DecodeJpegBenchmark(test.Benchmark):
start_time = time.time()
for _ in xrange(num_iters):
sess.run(r)
- return time.time() - start_time
+ end_time = time.time()
+ return end_time - start_time
def benchmarkDecodeJpegSmall(self):
"""Evaluate single DecodeImageOp for small size image."""
diff --git a/tensorflow/python/kernel_tests/depthwise_conv_op_test.py b/tensorflow/python/kernel_tests/depthwise_conv_op_test.py
index 7134e02c34..58845552db 100644
--- a/tensorflow/python/kernel_tests/depthwise_conv_op_test.py
+++ b/tensorflow/python/kernel_tests/depthwise_conv_op_test.py
@@ -90,7 +90,7 @@ def CheckGradConfigsToTest():
class DepthwiseConv2DTest(test.TestCase):
# This is testing that depthwise_conv2d and depthwise_conv2d_native
- # produce the same results. It also tests that NCHW and NWHC
+ # produce the same results. It also tests that NCHW and NHWC
# formats agree, by comparing the depthwise_conv2d_native with
# 'NCHW' format (with transposition) matches the 'NHWC' format using
# the higher level interface.
@@ -142,7 +142,7 @@ class DepthwiseConv2DTest(test.TestCase):
native_t1 = t1
strides = [1, stride, stride, 1]
if data_format == "NCHW":
- # Transpose from NWHC input to NCHW
+ # Transpose from NHWC input to NCHW
# Ex. [4, 5, 5, 48] to [4, 48, 5, 5]
native_t1 = array_ops.transpose(t1, [0, 3, 1, 2])
strides = [1, 1, stride, stride]
@@ -368,7 +368,7 @@ class DepthwiseConv2DTest(test.TestCase):
native_input = input_tensor
strides = [1, stride, stride, 1]
if data_format == "NCHW":
- # Transpose from NWHC input to NCHW
+ # Transpose from NHWC input to NCHW
# Ex. [4, 5, 5, 48] to [4, 48, 5, 5]
native_input = array_ops.transpose(input_tensor, [0, 3, 1, 2])
input_shape = [
diff --git a/tensorflow/python/kernel_tests/neon_depthwise_conv_op_test.py b/tensorflow/python/kernel_tests/neon_depthwise_conv_op_test.py
index d8ce9fffbd..3cbbd48c8c 100644
--- a/tensorflow/python/kernel_tests/neon_depthwise_conv_op_test.py
+++ b/tensorflow/python/kernel_tests/neon_depthwise_conv_op_test.py
@@ -82,7 +82,7 @@ def CheckGradConfigsToTest():
class DepthwiseConv2DTest(test.TestCase):
# This is testing that depthwise_conv2d and depthwise_conv2d_native
- # produce the same results. It also tests that NCHW and NWHC
+ # produce the same results. It also tests that NCHW and NHWC
# formats agree, by comparing the depthwise_conv2d_native with
# 'NCHW' format (with transposition) matches the 'NHWC' format using
# the higher level interface.
@@ -123,7 +123,7 @@ class DepthwiseConv2DTest(test.TestCase):
native_t1 = t1
strides = [1, stride, stride, 1]
if data_format == "NCHW":
- # Transpose from NWHC input to NCHW
+ # Transpose from NHWC input to NCHW
# Ex. [4, 5, 5, 48] to [4, 48, 5, 5]
native_t1 = array_ops.transpose(t1, [0, 3, 1, 2])
strides = [1, 1, stride, stride]
diff --git a/tensorflow/python/kernel_tests/random/random_ops_test.py b/tensorflow/python/kernel_tests/random/random_ops_test.py
index e4b5c3832a..0ef6a95cfc 100644
--- a/tensorflow/python/kernel_tests/random/random_ops_test.py
+++ b/tensorflow/python/kernel_tests/random/random_ops_test.py
@@ -24,13 +24,42 @@ from six.moves import xrange # pylint: disable=redefined-builtin
from tensorflow.python.eager import context
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import ops
+from tensorflow.python.framework import random_seed
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import random_ops
from tensorflow.python.ops import variables
from tensorflow.python.platform import test
-class RandomNormalTest(test.TestCase):
+class RandomOpTestCommon(test.TestCase):
+
+ # Checks that executing the same rng_func multiple times rarely produces the
+ # same result.
+ def _testSingleSessionNotConstant(self,
+ rng_func,
+ num,
+ dtype,
+ min_or_mean,
+ max_or_stddev,
+ use_gpu,
+ op_seed=None,
+ graph_seed=None):
+ with self.test_session(use_gpu=use_gpu, graph=ops.Graph()) as sess:
+ if graph_seed is not None:
+ random_seed.set_random_seed(graph_seed)
+ x = rng_func([num], min_or_mean, max_or_stddev, dtype=dtype, seed=op_seed)
+
+ y = sess.run(x)
+ z = sess.run(x)
+ w = sess.run(x)
+
+ # We use exact equality here. If the random-number generator is producing
+ # the same output, all three outputs will be bitwise identical.
+ self.assertTrue((not np.array_equal(y, z)) or
+ (not np.array_equal(z, w)) or (not np.array_equal(y, w)))
+
+
+class RandomNormalTest(RandomOpTestCommon):
def _Sampler(self, num, mu, sigma, dtype, use_gpu, seed=None):
@@ -90,6 +119,36 @@ class RandomNormalTest(test.TestCase):
diff = rnd2 - rnd1
self.assertTrue(np.linalg.norm(diff.eval()) > 0.1)
+ def testSingleSessionNotConstant(self):
+ for use_gpu in [False, True]:
+ for dt in dtypes.float16, dtypes.float32, dtypes.float64:
+ self._testSingleSessionNotConstant(
+ random_ops.random_normal, 100, dt, 0.0, 1.0, use_gpu=use_gpu)
+
+ def testSingleSessionOpSeedNotConstant(self):
+ for use_gpu in [False, True]:
+ for dt in dtypes.float16, dtypes.float32, dtypes.float64:
+ self._testSingleSessionNotConstant(
+ random_ops.random_normal,
+ 100,
+ dt,
+ 0.0,
+ 1.0,
+ use_gpu=use_gpu,
+ op_seed=1345)
+
+ def testSingleSessionGraphSeedNotConstant(self):
+ for use_gpu in [False, True]:
+ for dt in dtypes.float16, dtypes.float32, dtypes.float64:
+ self._testSingleSessionNotConstant(
+ random_ops.random_normal,
+ 100,
+ dt,
+ 0.0,
+ 1.0,
+ use_gpu=use_gpu,
+ graph_seed=965)
+
class TruncatedNormalTest(test.TestCase):
@@ -187,7 +246,7 @@ class TruncatedNormalTest(test.TestCase):
self.assertAllEqual(rnd1, rnd2)
-class RandomUniformTest(test.TestCase):
+class RandomUniformTest(RandomOpTestCommon):
def _Sampler(self, num, minv, maxv, dtype, use_gpu, seed=None):
@@ -291,6 +350,39 @@ class RandomUniformTest(test.TestCase):
diff = (rnd2 - rnd1).eval()
self.assertTrue(np.linalg.norm(diff) > 0.1)
+ def testSingleSessionNotConstant(self):
+ for use_gpu in [False, True]:
+ for dt in (dtypes.float16, dtypes.float32, dtypes.float64, dtypes.int32,
+ dtypes.int64):
+ self._testSingleSessionNotConstant(
+ random_ops.random_uniform, 100, dt, 0, 17, use_gpu=use_gpu)
+
+ def testSingleSessionOpSeedNotConstant(self):
+ for use_gpu in [False, True]:
+ for dt in (dtypes.float16, dtypes.float32, dtypes.float64, dtypes.int32,
+ dtypes.int64):
+ self._testSingleSessionNotConstant(
+ random_ops.random_uniform,
+ 100,
+ dt,
+ 10,
+ 20,
+ use_gpu=use_gpu,
+ op_seed=1345)
+
+ def testSingleSessionGraphSeedNotConstant(self):
+ for use_gpu in [False, True]:
+ for dt in (dtypes.float16, dtypes.float32, dtypes.float64, dtypes.int32,
+ dtypes.int64):
+ self._testSingleSessionNotConstant(
+ random_ops.random_uniform,
+ 100,
+ dt,
+ 20,
+ 200,
+ use_gpu=use_gpu,
+ graph_seed=965)
+
class RandomShapeTest(test.TestCase):
diff --git a/tensorflow/python/ops/boosted_trees_ops.py b/tensorflow/python/ops/boosted_trees_ops.py
index 868a4f6b84..f7cbfe0312 100644
--- a/tensorflow/python/ops/boosted_trees_ops.py
+++ b/tensorflow/python/ops/boosted_trees_ops.py
@@ -37,8 +37,19 @@ from tensorflow.python.training import saver
class PruningMode(object):
+ """Class for working with Pruning modes."""
NO_PRUNING, PRE_PRUNING, POST_PRUNING = range(0, 3)
+ _map = {'none': NO_PRUNING, 'pre': PRE_PRUNING, 'post': POST_PRUNING}
+
+ @classmethod
+ def from_str(cls, mode):
+ if mode in cls._map:
+ return cls._map[mode]
+ else:
+ raise ValueError('pruning_mode mode must be one of: {}'.format(', '.join(
+ sorted(cls._map))))
+
class _TreeEnsembleSavable(saver.BaseSaverBuilder.SaveableObject):
"""SaveableObject implementation for TreeEnsemble."""
diff --git a/tensorflow/python/ops/cond_v2_impl.py b/tensorflow/python/ops/cond_v2_impl.py
index 5cd0cb34de..44c5c050c0 100644
--- a/tensorflow/python/ops/cond_v2_impl.py
+++ b/tensorflow/python/ops/cond_v2_impl.py
@@ -58,12 +58,14 @@ def cond_v2(pred, true_fn, false_fn, name="cond"):
with ops.name_scope(name) as scope:
# Identify if there is a caller device, & get the innermost if possible.
- device_stack = ops.get_default_graph()._device_function_stack
- caller_device = device_stack[-1] if device_stack else None
+ # pylint: disable=protected-access
+ device_funcs = ops.get_default_graph()._device_functions_outer_to_inner
+ caller_device = device_funcs[-1] if device_funcs else None
caller_colocation_stack = ops.get_default_graph()._colocation_stack
caller_container = ops.get_default_graph()._container
caller_collection_ref = ops.get_default_graph()._collections
+ # pylint: enable=protected-access
func_name_prefix = scope.replace("/", "_")
@@ -106,7 +108,7 @@ def cond_v2(pred, true_fn, false_fn, name="cond"):
false_graph.outputs.extend(extra_false_outputs)
# Create the If op.
- tensors = gen_functional_ops._if(
+ tensors = gen_functional_ops._if( # pylint: disable=protected-access
pred, cond_inputs, [t.dtype for t in true_graph.outputs],
_create_new_tf_function(true_graph),
_create_new_tf_function(false_graph),
@@ -125,8 +127,10 @@ def cond_v2(pred, true_fn, false_fn, name="cond"):
# TODO(b/110167197) this approach requires cond_v2 to have at least 1 output
if_op = tensors[0].op
if not control_flow_util.IsInXLAContext(if_op):
+ # pylint: disable=protected-access
if_op._set_attr("_lower_using_switch_merge",
attr_value_pb2.AttrValue(b=True))
+ # pylint: enable=protected-access
return tensors[:num_cond_outputs]
diff --git a/tensorflow/python/ops/control_flow_ops.py b/tensorflow/python/ops/control_flow_ops.py
index 888075ba2e..aeac61c005 100644
--- a/tensorflow/python/ops/control_flow_ops.py
+++ b/tensorflow/python/ops/control_flow_ops.py
@@ -1817,15 +1817,34 @@ class CondContext(ControlFlowContext):
def _AddOpInternal(self, op):
"""Add `op` to the current context."""
if not op.inputs:
- # Remove any external control dependency on this op
+ # If we're in a while loop, remove any control inputs from outside the
+ # loop.
self._RemoveExternalControlEdges(op)
- # pylint: disable=protected-access
- op._add_control_input(self._pivot.op)
- # pylint: enable=protected-access
+
+ if not any(util.OpInContext(input_op, self)
+ for input_op in op.control_inputs):
+ # pylint: disable=protected-access
+ op._add_control_input(self._pivot.op)
+ # pylint: enable=protected-access
else:
+ # Make each input to 'op' available in this CondContext. If an input is
+ # already part of this context there's nothing to do, but if it's
+ # external, AddValue() will handle adding the appropriate Switch node and
+ # other bookkeeping.
for index in range(len(op.inputs)):
x = op.inputs[index]
- real_x = self.AddValue(x)
+ if op.type == "Merge" and x.op.type == "NextIteration":
+ # Edge case: if we're importing a while loop inside this CondContext,
+ # AddValue() will not correctly handle the NextIteration inputs to
+ # Merge node. The problem is that the NextIteration should also be
+ # part of this context, but if we're importing it won't have been
+ # processed and added to the context yet, so AddValue() will try to
+ # add a Switch which results in an invalid graph. Instead, we use the
+ # NextIteration input as-is here, and it will eventually be added to
+ # the context via AddOp().
+ real_x = x
+ else:
+ real_x = self.AddValue(x)
if real_x != x:
# pylint: disable=protected-access
op._update_input(index, real_x)
diff --git a/tensorflow/python/ops/control_flow_util.py b/tensorflow/python/ops/control_flow_util.py
index 7a18986c5b..72c074ed1a 100644
--- a/tensorflow/python/ops/control_flow_util.py
+++ b/tensorflow/python/ops/control_flow_util.py
@@ -214,6 +214,14 @@ def IsContainingContext(ctxt, maybe_containing_ctxt):
return True
+def OpInContext(op, ctxt):
+ return IsContainingContext(op._get_control_flow_context(), ctxt) # pylint: disable=protected-access
+
+
+def TensorInContext(tensor, ctxt):
+ return OpInContext(tensor.op, ctxt)
+
+
def CheckInputFromValidContext(op, input_op):
"""Returns whether `input_op` can be used from `op`s context.
diff --git a/tensorflow/python/ops/image_ops_impl.py b/tensorflow/python/ops/image_ops_impl.py
index 9440bab9ee..855a4d0c33 100644
--- a/tensorflow/python/ops/image_ops_impl.py
+++ b/tensorflow/python/ops/image_ops_impl.py
@@ -20,6 +20,7 @@ from __future__ import print_function
import numpy as np
+from tensorflow.python.compat import compat
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import ops
@@ -2110,6 +2111,64 @@ def non_max_suppression(boxes,
iou_threshold, score_threshold)
+@tf_export('image.non_max_suppression_padded')
+def non_max_suppression_padded(boxes,
+ scores,
+ max_output_size,
+ iou_threshold=0.5,
+ score_threshold=float('-inf'),
+ pad_to_max_output_size=False,
+ name=None):
+ """Greedily selects a subset of bounding boxes in descending order of score.
+
+ Performs algorithmically equivalent operation to tf.image.non_max_suppression,
+ with the addition of an optional parameter which zero-pads the output to
+ be of size `max_output_size`.
+ The output of this operation is a tuple containing the set of integers
+ indexing into the input collection of bounding boxes representing the selected
+ boxes and the number of valid indices in the index set. The bounding box
+ coordinates corresponding to the selected indices can then be obtained using
+ the `tf.slice` and `tf.gather` operations. For example:
+ selected_indices_padded, num_valid = tf.image.non_max_suppression_padded(
+ boxes, scores, max_output_size, iou_threshold,
+ score_threshold, pad_to_max_output_size=True)
+ selected_indices = tf.slice(
+ selected_indices_padded, tf.constant([0]), num_valid)
+ selected_boxes = tf.gather(boxes, selected_indices)
+
+ Args:
+ boxes: A 2-D float `Tensor` of shape `[num_boxes, 4]`.
+ scores: A 1-D float `Tensor` of shape `[num_boxes]` representing a single
+ score corresponding to each box (each row of boxes).
+ max_output_size: A scalar integer `Tensor` representing the maximum number
+ of boxes to be selected by non max suppression.
+ iou_threshold: A float representing the threshold for deciding whether boxes
+ overlap too much with respect to IOU.
+ score_threshold: A float representing the threshold for deciding when to
+ remove boxes based on score.
+ pad_to_max_output_size: bool. If True, size of `selected_indices` output
+ is padded to `max_output_size`.
+ name: A name for the operation (optional).
+
+ Returns:
+ selected_indices: A 1-D integer `Tensor` of shape `[M]` representing the
+ selected indices from the boxes tensor, where `M <= max_output_size`.
+ valid_outputs: A scalar integer `Tensor` denoting how many elements in
+ `selected_indices` are valid. Valid elements occur first, then padding.
+ """
+ with ops.name_scope(name, 'non_max_suppression_padded'):
+ iou_threshold = ops.convert_to_tensor(iou_threshold, name='iou_threshold')
+ score_threshold = ops.convert_to_tensor(
+ score_threshold, name='score_threshold')
+ if compat.forward_compatible(2018, 8, 7) or pad_to_max_output_size:
+ return gen_image_ops.non_max_suppression_v4(
+ boxes, scores, max_output_size, iou_threshold, score_threshold,
+ pad_to_max_output_size)
+ else:
+ return gen_image_ops.non_max_suppression_v3(
+ boxes, scores, max_output_size, iou_threshold, score_threshold)
+
+
@tf_export('image.non_max_suppression_overlaps')
def non_max_suppression_with_overlaps(overlaps,
scores,
diff --git a/tensorflow/python/ops/rnn.py b/tensorflow/python/ops/rnn.py
index 7096e0dd84..7b6ab20975 100644
--- a/tensorflow/python/ops/rnn.py
+++ b/tensorflow/python/ops/rnn.py
@@ -432,9 +432,15 @@ def bidirectional_dynamic_rnn(cell_fw, cell_bw, inputs, sequence_length=None,
return array_ops.reverse(input_, axis=[seq_axis])
with vs.variable_scope("bw") as bw_scope:
- inputs_reverse = _reverse(
- inputs, seq_lengths=sequence_length,
- seq_axis=time_axis, batch_axis=batch_axis)
+
+ def _map_reverse(inp):
+ return _reverse(
+ inp,
+ seq_lengths=sequence_length,
+ seq_axis=time_axis,
+ batch_axis=batch_axis)
+
+ inputs_reverse = nest.map_structure(_map_reverse, inputs)
tmp, output_state_bw = dynamic_rnn(
cell=cell_bw, inputs=inputs_reverse, sequence_length=sequence_length,
initial_state=initial_state_bw, dtype=dtype,
diff --git a/tensorflow/python/ops/rnn_cell_impl.py b/tensorflow/python/ops/rnn_cell_impl.py
index 70805fd572..42806ba6ec 100644
--- a/tensorflow/python/ops/rnn_cell_impl.py
+++ b/tensorflow/python/ops/rnn_cell_impl.py
@@ -1261,6 +1261,11 @@ class MultiRNNCell(RNNCell):
raise TypeError(
"cells must be a list or tuple, but saw: %s." % cells)
+ if len(set([id(cell) for cell in cells])) < len(cells):
+ logging.log_first_n(logging.WARN,
+ "At least two cells provided to MultiRNNCell "
+ "are the same object and will share weights.", 1)
+
self._cells = cells
for cell_number, cell in enumerate(self._cells):
# Add Checkpointable dependencies on these cells so their variables get
diff --git a/tensorflow/python/saved_model/builder_impl.py b/tensorflow/python/saved_model/builder_impl.py
index e58be804c2..8c985a7c2f 100644
--- a/tensorflow/python/saved_model/builder_impl.py
+++ b/tensorflow/python/saved_model/builder_impl.py
@@ -34,6 +34,7 @@ from tensorflow.python.platform import tf_logging
from tensorflow.python.saved_model import constants
from tensorflow.python.training import saver as tf_saver
from tensorflow.python.util import compat
+from tensorflow.python.util.deprecation import deprecated_args
from tensorflow.python.util.tf_export import tf_export
@@ -133,39 +134,32 @@ class SavedModelBuilder(object):
tf_logging.info("Assets written to: %s",
compat.as_text(assets_destination_dir))
- def _maybe_add_legacy_init_op(self, legacy_init_op=None):
- """Add legacy init op to the SavedModel.
+ def _maybe_add_main_op(self, main_op):
+ """Adds main op to the SavedModel.
Args:
- legacy_init_op: Optional legacy init op to support backward compatibility.
+ main_op: Main op to run as part of graph initialization. If None, no
+ main op will be added to the graph.
Raises:
- TypeError if legacy init op is not of type `Operation`.
- AssertionError if the graph already contains one or more legacy init ops.
+ TypeError: if main op is provided but is not of type `Operation`.
+ ValueError: if the Graph already contains an init op.
"""
- if legacy_init_op is not None:
- if not isinstance(legacy_init_op, ops.Operation):
- raise TypeError("legacy_init_op needs to be an Operation: %r" %
- legacy_init_op)
- if ops.get_collection(constants.LEGACY_INIT_OP_KEY):
- raise AssertionError(
- "graph already contains one or more legacy init ops under the "
- "collection {}.".format(constants.LEGACY_INIT_OP_KEY))
- ops.add_to_collection(constants.LEGACY_INIT_OP_KEY, legacy_init_op)
-
- def _add_main_op(self, main_op):
- """Add main op to the SavedModel.
+ if main_op is None:
+ return
- Args:
- main_op: Main op to run as part of graph initialization.
+ if not isinstance(main_op, ops.Operation):
+ raise TypeError("main_op needs to be an Operation: %r" % main_op)
- Raises:
- TypeError if main op is not of type `Operation`.
- """
- if main_op is not None:
- if not isinstance(main_op, ops.Operation):
- raise TypeError("main_op needs to be an Operation: %r" % main_op)
- ops.add_to_collection(constants.MAIN_OP_KEY, main_op)
+ # Validate that no other init ops have been added to this graph already.
+ # We check main_op and legacy_init_op for thoroughness and explicitness.
+ for init_op_key in (constants.MAIN_OP_KEY, constants.LEGACY_INIT_OP_KEY):
+ if ops.get_collection(init_op_key):
+ raise ValueError(
+ "Graph already contains one or more main ops under the "
+ "collection {}.".format(init_op_key))
+
+ ops.add_to_collection(constants.MAIN_OP_KEY, main_op)
def _add_train_op(self, train_op):
"""Add train op to the SavedModel.
@@ -257,16 +251,12 @@ class SavedModelBuilder(object):
self._validate_tensor_info(outputs[outputs_key])
def _add_collections(
- self, assets_collection, legacy_init_op, main_op, train_op):
+ self, assets_collection, main_op, train_op):
"""Add asset and op collections to be saved."""
# Save asset files and write them to disk, if any.
self._save_and_write_assets(assets_collection)
- if main_op is None:
- # Add legacy init op to the SavedModel.
- self._maybe_add_legacy_init_op(legacy_init_op)
- else:
- self._add_main_op(main_op)
+ self._maybe_add_main_op(main_op)
self._add_train_op(train_op)
@@ -282,6 +272,9 @@ class SavedModelBuilder(object):
allow_empty=True)
return saver
+ @deprecated_args(None,
+ "Pass your op to the equivalent parameter main_op instead.",
+ "legacy_init_op")
def add_meta_graph(self,
tags,
signature_def_map=None,
@@ -306,7 +299,7 @@ class SavedModelBuilder(object):
that this collection should be a subset of the assets saved as part of
the first meta graph in the SavedModel.
legacy_init_op: Legacy support for op or group of ops to execute after the
- restore op upon a load.
+ restore op upon a load. Deprecated; please use main_op instead.
clear_devices: Set to true if the device info on the default graph should
be cleared.
main_op: Op or group of ops to execute when the graph is loaded. Note
@@ -333,8 +326,12 @@ class SavedModelBuilder(object):
# properly populated.
self._validate_signature_def_map(signature_def_map)
+ # legacy_init_op is deprecated, and going away in TF 2.0.
+ # Re-mapping to main_op, as treatment is identical regardless.
+ main_op = main_op or legacy_init_op
+
# Add assets and ops
- self._add_collections(assets_collection, legacy_init_op, main_op, None)
+ self._add_collections(assets_collection, main_op, None)
saver = self._maybe_create_saver(saver)
@@ -351,6 +348,9 @@ class SavedModelBuilder(object):
# Tag the meta graph def and add it to the SavedModel.
self._tag_and_add_meta_graph(meta_graph_def, tags, signature_def_map)
+ @deprecated_args(None,
+ "Pass your op to the equivalent parameter main_op instead.",
+ "legacy_init_op")
def add_meta_graph_and_variables(self,
sess,
tags,
@@ -378,7 +378,7 @@ class SavedModelBuilder(object):
def.
assets_collection: Assets collection to be saved with SavedModel.
legacy_init_op: Legacy support for op or group of ops to execute after the
- restore op upon a load.
+ restore op upon a load. Deprecated; please use main_op instead.
clear_devices: Set to true if the device info on the default graph should
be cleared.
main_op: Op or group of ops to execute when the graph is loaded. Note
@@ -402,8 +402,12 @@ class SavedModelBuilder(object):
# properly populated.
self._validate_signature_def_map(signature_def_map)
+ # legacy_init_op is deprecated, and going away in TF 2.0.
+ # Re-mapping to main_op, as treatment is identical regardless.
+ main_op = main_op or legacy_init_op
+
# Add assets and ops
- self._add_collections(assets_collection, legacy_init_op, main_op, None)
+ self._add_collections(assets_collection, main_op, None)
# Create the variables sub-directory, if it does not exist.
variables_dir = os.path.join(
diff --git a/tensorflow/python/saved_model/constants.py b/tensorflow/python/saved_model/constants.py
index 61c6ffbd0d..cb251f08bb 100644
--- a/tensorflow/python/saved_model/constants.py
+++ b/tensorflow/python/saved_model/constants.py
@@ -60,6 +60,10 @@ SAVED_MODEL_FILENAME_PBTXT = "saved_model.pbtxt"
tf_export("saved_model.constants.SAVED_MODEL_FILENAME_PBTXT").export_constant(
__name__, "SAVED_MODEL_FILENAME_PBTXT")
+# File name for json format of SavedModel.
+# Not exported while keras_saved_model is in contrib.
+SAVED_MODEL_FILENAME_JSON = "saved_model.json"
+
# Subdirectory name containing the variables/checkpoint files.
VARIABLES_DIRECTORY = "variables"
tf_export("saved_model.constants.VARIABLES_DIRECTORY").export_constant(
@@ -69,5 +73,3 @@ tf_export("saved_model.constants.VARIABLES_DIRECTORY").export_constant(
VARIABLES_FILENAME = "variables"
tf_export("saved_model.constants.VARIABLES_FILENAME").export_constant(
__name__, "VARIABLES_FILENAME")
-
-
diff --git a/tensorflow/python/saved_model/loader_impl.py b/tensorflow/python/saved_model/loader_impl.py
index e5f649fdab..16077f52fa 100644
--- a/tensorflow/python/saved_model/loader_impl.py
+++ b/tensorflow/python/saved_model/loader_impl.py
@@ -116,11 +116,14 @@ def _get_asset_tensors(export_dir, meta_graph_def_to_load, import_scope=None):
return asset_tensor_dict
-def _get_main_op_tensor(meta_graph_def_to_load):
+def _get_main_op_tensor(
+ meta_graph_def_to_load, init_op_key=constants.MAIN_OP_KEY):
"""Gets the main op tensor, if one exists.
Args:
meta_graph_def_to_load: The meta graph def from the SavedModel to be loaded.
+ init_op_key: name of collection to check; should be one of MAIN_OP_KEY
+ or the deprecated LEGACY_INIT_OP_KEY
Returns:
The main op tensor, if it exists and `None` otherwise.
@@ -131,38 +134,15 @@ def _get_main_op_tensor(meta_graph_def_to_load):
"""
collection_def = meta_graph_def_to_load.collection_def
main_op_tensor = None
- if constants.MAIN_OP_KEY in collection_def:
- main_ops = collection_def[constants.MAIN_OP_KEY].node_list.value
+ if init_op_key in collection_def:
+ main_ops = collection_def[init_op_key].node_list.value
if len(main_ops) != 1:
- raise RuntimeError("Expected exactly one SavedModel main op.")
- main_op_tensor = ops.get_collection(constants.MAIN_OP_KEY)[0]
+ raise RuntimeError("Expected exactly one SavedModel main op. "
+ "Found: {}".format(main_ops))
+ main_op_tensor = ops.get_collection(init_op_key)[0]
return main_op_tensor
-def _get_legacy_init_op_tensor(meta_graph_def_to_load):
- """Gets the legacy init op tensor, if one exists.
-
- Args:
- meta_graph_def_to_load: The meta graph def from the SavedModel to be loaded.
-
- Returns:
- The legacy init op tensor, if it exists and `None` otherwise.
-
- Raises:
- RuntimeError: If the collection def corresponding to the legacy init op key
- has other than exactly one tensor.
- """
- collection_def = meta_graph_def_to_load.collection_def
- legacy_init_op_tensor = None
- if constants.LEGACY_INIT_OP_KEY in collection_def:
- legacy_init_ops = collection_def[
- constants.LEGACY_INIT_OP_KEY].node_list.value
- if len(legacy_init_ops) != 1:
- raise RuntimeError("Expected exactly one legacy serving init op.")
- legacy_init_op_tensor = ops.get_collection(constants.LEGACY_INIT_OP_KEY)[0]
- return legacy_init_op_tensor
-
-
@tf_export("saved_model.loader.maybe_saved_model_directory")
def maybe_saved_model_directory(export_dir):
"""Checks whether the provided export directory could contain a SavedModel.
@@ -284,12 +264,15 @@ class SavedModelLoader(object):
**saver_kwargs: keyword arguments to pass to tf.train.import_meta_graph.
Returns:
- Saver defined by the MetaGraph, which can be used to restore the variable
- values.
+ A tuple of
+ * Saver defined by the MetaGraph, which can be used to restore the
+ variable values.
+ * List of `Operation`/`Tensor` objects returned from
+ `tf.import_graph_def` (may be `None`).
"""
meta_graph_def = self.get_meta_graph_def_from_tags(tags)
with graph.as_default():
- return tf_saver.import_meta_graph(
+ return tf_saver._import_meta_graph_with_return_elements( # pylint: disable=protected-access
meta_graph_def, import_scope=import_scope, **saver_kwargs)
def restore_variables(self, sess, saver, import_scope=None):
@@ -340,8 +323,8 @@ class SavedModelLoader(object):
self._export_dir, meta_graph_def, import_scope=import_scope)
main_op_tensor = (
- _get_main_op_tensor(meta_graph_def) or
- (_get_legacy_init_op_tensor(meta_graph_def)))
+ _get_main_op_tensor(meta_graph_def, constants.MAIN_OP_KEY) or
+ _get_main_op_tensor(meta_graph_def, constants.LEGACY_INIT_OP_KEY))
if main_op_tensor is not None:
sess.run(fetches=[main_op_tensor], feed_dict=asset_tensors_dictionary)
@@ -361,8 +344,8 @@ class SavedModelLoader(object):
`MetagraphDef` proto of the graph that was loaded.
"""
with sess.graph.as_default():
- saver = self.load_graph(sess.graph, tags, import_scope,
- **saver_kwargs)
+ saver, _ = self.load_graph(sess.graph, tags, import_scope,
+ **saver_kwargs)
self.restore_variables(sess, saver, import_scope)
self.run_init_ops(sess, tags, import_scope)
return self.get_meta_graph_def_from_tags(tags)
diff --git a/tensorflow/python/saved_model/loader_test.py b/tensorflow/python/saved_model/loader_test.py
index ce18859f6b..9a0b276a4b 100644
--- a/tensorflow/python/saved_model/loader_test.py
+++ b/tensorflow/python/saved_model/loader_test.py
@@ -111,7 +111,8 @@ class SavedModelLoaderTest(test.TestCase):
def test_load_with_import_scope(self):
loader = loader_impl.SavedModelLoader(SAVED_MODEL_WITH_MAIN_OP)
with self.test_session(graph=ops.Graph()) as sess:
- saver = loader.load_graph(sess.graph, ["foo_graph"], import_scope="baz")
+ saver, _ = loader.load_graph(
+ sess.graph, ["foo_graph"], import_scope="baz")
# The default saver should not work when the import scope is set.
with self.assertRaises(errors.NotFoundError):
@@ -149,7 +150,7 @@ class SavedModelLoaderTest(test.TestCase):
def test_run_init_op(self):
loader = loader_impl.SavedModelLoader(SAVED_MODEL_WITH_MAIN_OP)
graph = ops.Graph()
- saver = loader.load_graph(graph, ["foo_graph"])
+ saver, _ = loader.load_graph(graph, ["foo_graph"])
with self.test_session(graph=graph) as sess:
loader.restore_variables(sess, saver)
self.assertEqual(5, sess.graph.get_tensor_by_name("x:0").eval())
@@ -203,7 +204,7 @@ class SavedModelLoaderTest(test.TestCase):
loader = loader_impl.SavedModelLoader(path)
with self.test_session(graph=ops.Graph()) as sess:
- saver = loader.load_graph(sess.graph, ["foo_graph"])
+ saver, _ = loader.load_graph(sess.graph, ["foo_graph"])
self.assertFalse(variables._all_saveable_objects())
self.assertIsNotNone(saver)
@@ -212,6 +213,18 @@ class SavedModelLoaderTest(test.TestCase):
self.assertEqual(5, sess.graph.get_tensor_by_name("x:0").eval())
self.assertEqual(11, sess.graph.get_tensor_by_name("y:0").eval())
+ def test_load_saved_model_graph_with_return_elements(self):
+ """Ensure that the correct elements are returned."""
+ loader = loader_impl.SavedModelLoader(SIMPLE_ADD_SAVED_MODEL)
+ graph = ops.Graph()
+ _, ret = loader.load_graph(graph, ["foo_graph"],
+ return_elements=["y:0", "x:0"])
+
+ self.assertEqual(graph.get_tensor_by_name("y:0"), ret[0])
+ self.assertEqual(graph.get_tensor_by_name("x:0"), ret[1])
+
+ with self.assertRaisesRegexp(ValueError, "not found in graph"):
+ loader.load_graph(graph, ["foo_graph"], return_elements=["z:0"])
if __name__ == "__main__":
test.main()
diff --git a/tensorflow/python/saved_model/saved_model_test.py b/tensorflow/python/saved_model/saved_model_test.py
index fb4732aca2..00b669fc97 100644
--- a/tensorflow/python/saved_model/saved_model_test.py
+++ b/tensorflow/python/saved_model/saved_model_test.py
@@ -846,9 +846,19 @@ class SavedModelTest(test.TestCase):
def testLegacyInitOpWithNonEmptyCollection(self):
export_dir = self._get_export_dir(
"test_legacy_init_op_with_non_empty_collection")
+ self._testInitOpsWithNonEmptyCollection(
+ export_dir, constants.LEGACY_INIT_OP_KEY)
+
+ def testMainOpWithNonEmptyCollection(self):
+ export_dir = self._get_export_dir(
+ "test_main_op_with_non_empty_collection")
+ self._testInitOpsWithNonEmptyCollection(export_dir, constants.MAIN_OP_KEY)
+
+ def _testInitOpsWithNonEmptyCollection(self, export_dir, key):
builder = saved_model_builder.SavedModelBuilder(export_dir)
- with self.test_session(graph=ops.Graph()) as sess:
+ g = ops.Graph()
+ with self.test_session(graph=g) as sess:
# Initialize variable `v1` to 1.
v1 = variables.Variable(1, name="v1")
ops.add_to_collection("v", v1)
@@ -857,19 +867,21 @@ class SavedModelTest(test.TestCase):
v2 = variables.Variable(42, name="v2", trainable=False, collections=[])
ops.add_to_collection("v", v2)
- # Set up an assignment op to be run as part of the legacy_init_op.
+ # Set up an assignment op to be run as part of the init op.
assign_v2 = state_ops.assign(v2, v1)
- legacy_init_op = control_flow_ops.group(assign_v2, name="legacy_init_op")
+ init_op = control_flow_ops.group(assign_v2, name="init_op")
sess.run(variables.global_variables_initializer())
- ops.add_to_collection(constants.LEGACY_INIT_OP_KEY,
- control_flow_ops.no_op())
- # AssertionError should be raised since the LEGACY_INIT_OP_KEY collection
+ ops.add_to_collection(key, control_flow_ops.no_op())
+ # ValueError should be raised since the LEGACY_INIT_OP_KEY collection
# is not empty and we don't support multiple init ops.
- with self.assertRaises(AssertionError):
+ with self.assertRaisesRegexp(ValueError, "Graph already contains"):
builder.add_meta_graph_and_variables(
- sess, ["foo"], legacy_init_op=legacy_init_op)
+ sess, ["foo"], legacy_init_op=init_op)
+ # We shouldn't be able to add as MAIN_OP, either.
+ with self.assertRaisesRegexp(ValueError, "Graph already contains"):
+ builder.add_meta_graph_and_variables(sess, ["foo"], main_op=init_op)
def testTrainOp(self):
export_dir = self._get_export_dir("test_train_op")
diff --git a/tensorflow/python/summary/writer/writer.py b/tensorflow/python/summary/writer/writer.py
index aca084fc91..60e96ee947 100644
--- a/tensorflow/python/summary/writer/writer.py
+++ b/tensorflow/python/summary/writer/writer.py
@@ -325,7 +325,7 @@ class FileWriter(SummaryToEventTransformer):
```
The `session` argument to the constructor makes the returned `FileWriter` a
- a compatibility layer over new graph-based summaries (`tf.contrib.summary`).
+ compatibility layer over new graph-based summaries (`tf.contrib.summary`).
Crucially, this means the underlying writer resource and events file will
be shared with any other `FileWriter` using the same `session` and `logdir`,
and with any `tf.contrib.summary.SummaryWriter` in this session using the
diff --git a/tensorflow/python/tools/freeze_graph.py b/tensorflow/python/tools/freeze_graph.py
index e9f1def48c..4349699a94 100644
--- a/tensorflow/python/tools/freeze_graph.py
+++ b/tensorflow/python/tools/freeze_graph.py
@@ -38,6 +38,7 @@ from __future__ import division
from __future__ import print_function
import argparse
+import re
import sys
from google.protobuf import text_format
@@ -116,16 +117,43 @@ def freeze_graph_with_def_protos(input_graph_def,
var_list = {}
reader = pywrap_tensorflow.NewCheckpointReader(input_checkpoint)
var_to_shape_map = reader.get_variable_to_shape_map()
+
+ # List of all partition variables. Because the condition is heuristic
+ # based, the list could include false positives.
+ all_parition_variable_names = [
+ tensor.name.split(":")[0]
+ for op in sess.graph.get_operations()
+ for tensor in op.values()
+ if re.search(r"/part_\d+/", tensor.name)
+ ]
+ has_partition_var = False
+
for key in var_to_shape_map:
try:
tensor = sess.graph.get_tensor_by_name(key + ":0")
+ if any(key in name for name in all_parition_variable_names):
+ has_partition_var = True
except KeyError:
# This tensor doesn't exist in the graph (for example it's
# 'global_step' or a similar housekeeping element) so skip it.
continue
var_list[key] = tensor
- saver = saver_lib.Saver(
- var_list=var_list, write_version=checkpoint_version)
+
+ try:
+ saver = saver_lib.Saver(
+ var_list=var_list, write_version=checkpoint_version)
+ except TypeError as e:
+ # `var_list` is required to be a map of variable names to Variable
+ # tensors. Partition variables are Identity tensors that cannot be
+ # handled by Saver.
+ if has_partition_var:
+ print("Models containing partition variables cannot be converted "
+ "from checkpoint files. Please pass in a SavedModel using "
+ "the flag --input_saved_model_dir.")
+ return -1
+ else:
+ raise e
+
saver.restore(sess, input_checkpoint)
if initializer_nodes:
sess.run(initializer_nodes.replace(" ", "").split(","))
diff --git a/tensorflow/python/tools/freeze_graph_test.py b/tensorflow/python/tools/freeze_graph_test.py
index 91f0061ebc..e38945fabc 100644
--- a/tensorflow/python/tools/freeze_graph_test.py
+++ b/tensorflow/python/tools/freeze_graph_test.py
@@ -19,6 +19,7 @@ from __future__ import division
from __future__ import print_function
import os
+import re
from tensorflow.core.example import example_pb2
from tensorflow.core.framework import graph_pb2
@@ -31,7 +32,10 @@ from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import math_ops
+from tensorflow.python.ops import nn
from tensorflow.python.ops import parsing_ops
+from tensorflow.python.ops import partitioned_variables
+from tensorflow.python.ops import variable_scope
from tensorflow.python.ops import variables
from tensorflow.python.platform import test
from tensorflow.python.saved_model import builder as saved_model_builder
@@ -262,6 +266,69 @@ class FreezeGraphTest(test_util.TensorFlowTestCase):
output = sess.run(output_node, feed_dict={input_node: [example]})
self.assertNear(feature_value, output, 0.00001)
+ def testSinglePartitionedVariable(self):
+ """Ensures partitioned variables fail cleanly with freeze graph."""
+ checkpoint_prefix = os.path.join(self.get_temp_dir(), "saved_checkpoint")
+ checkpoint_state_name = "checkpoint_state"
+ input_graph_name = "input_graph.pb"
+ output_graph_name = "output_graph.pb"
+
+ # Create a graph with partition variables. When weights are partitioned into
+ # a single partition, the weights variable is followed by a identity ->
+ # identity (an additional identity node).
+ partitioner = partitioned_variables.fixed_size_partitioner(1)
+ with ops.Graph().as_default():
+ with variable_scope.variable_scope("part", partitioner=partitioner):
+ batch_size, height, width, depth = 5, 128, 128, 3
+ input1 = array_ops.zeros(
+ (batch_size, height, width, depth), name="input1")
+ input2 = array_ops.zeros(
+ (batch_size, height, width, depth), name="input2")
+
+ num_nodes = depth
+ filter1 = variable_scope.get_variable("filter", [num_nodes, num_nodes])
+ filter2 = array_ops.reshape(filter1, [1, 1, num_nodes, num_nodes])
+ conv = nn.conv2d(
+ input=input1, filter=filter2, strides=[1, 1, 1, 1], padding="SAME")
+ node = math_ops.add(conv, input2, name="test/add")
+ node = nn.relu6(node, name="test/relu6")
+
+ # Save graph and checkpoints.
+ sess = session.Session()
+ sess.run(variables.global_variables_initializer())
+
+ saver = saver_lib.Saver()
+ checkpoint_path = saver.save(
+ sess,
+ checkpoint_prefix,
+ global_step=0,
+ latest_filename=checkpoint_state_name)
+ graph_io.write_graph(sess.graph, self.get_temp_dir(), input_graph_name)
+
+ # Ensure this graph has partition variables.
+ self.assertTrue([
+ tensor.name.split(":")[0]
+ for op in sess.graph.get_operations()
+ for tensor in op.values()
+ if re.search(r"/part_\d+/", tensor.name)
+ ])
+
+ # Test freezing graph doesn't make it crash.
+ output_node_names = "save/restore_all"
+ output_graph_path = os.path.join(self.get_temp_dir(), output_graph_name)
+
+ return_value = freeze_graph.freeze_graph_with_def_protos(
+ input_graph_def=sess.graph_def,
+ input_saver_def=None,
+ input_checkpoint=checkpoint_path,
+ output_node_names=output_node_names,
+ restore_op_name="save/restore_all", # default value
+ filename_tensor_name="save/Const:0", # default value
+ output_graph=output_graph_path,
+ clear_devices=False,
+ initializer_nodes="")
+ self.assertTrue(return_value, -1)
+
if __name__ == "__main__":
test.main()
diff --git a/tensorflow/python/training/checkpoint_utils.py b/tensorflow/python/training/checkpoint_utils.py
index 883f4fd910..a052081630 100644
--- a/tensorflow/python/training/checkpoint_utils.py
+++ b/tensorflow/python/training/checkpoint_utils.py
@@ -24,7 +24,6 @@ from tensorflow.python import pywrap_tensorflow
from tensorflow.python.framework import ops
from tensorflow.python.ops import io_ops
from tensorflow.python.ops import resource_variable_ops
-from tensorflow.python.ops import state_ops
from tensorflow.python.ops import variable_scope as vs
from tensorflow.python.ops import variables
from tensorflow.python.platform import gfile
@@ -308,32 +307,19 @@ def _set_checkpoint_initializer(variable,
restore_op = io_ops.restore_v2(
ckpt_file, [tensor_name], [slice_spec], [base_type], name=name)[0]
- # TODO(priyag, allenl): Use `SaveableObject.restore` instead here.
- if resource_variable_ops.is_resource_variable(variable):
- init_op = variable.assign(restore_op, read_value=False)
- else:
- init_op = state_ops.assign(variable, restore_op)
+ names_to_saveables = saver.BaseSaverBuilder.OpListToDict([variable])
+ saveable_objects = []
+ for name, op in names_to_saveables.items():
+ for s in saver.BaseSaverBuilder.SaveableObjectsForOp(op, name):
+ saveable_objects.append(s)
+
+ assert len(saveable_objects) == 1 # Should be only one variable.
+ init_op = saveable_objects[0].restore([restore_op], restored_shapes=None)
# pylint:disable=protected-access
- # We need special handling for `DistributedVariable`s as they contain
- # mutliple actual variables. `assign` on a `DistributedVariable` returns a
- # combined `init_op` which contains initializers for all the contained
- # variables. We then set each underlying variable's `_initializer_op` using
- # the corresponding `init_op`.
- # TODO(priyag): Use `isinstance` checks when `DistributedVariable` class
- # moves out of contrib.
- if any(base.__name__ == "DistributedVariable"
- for base in variable.__class__.__bases__):
- assert distribute_lib.get_cross_tower_context()
- assert hasattr(variable, "_index")
- for (d, v) in six.iteritems(variable._index):
- v._initializer_op = init_op._index[d]
- restore_op.set_shape(v.shape)
- v._initial_value = restore_op
- else:
- variable._initializer_op = init_op
- restore_op.set_shape(variable.shape)
- variable._initial_value = restore_op
+ variable._initializer_op = init_op
+ restore_op.set_shape(variable.shape)
+ variable._initial_value = restore_op
# pylint:enable=protected-access
diff --git a/tensorflow/python/training/checkpoint_utils_test.py b/tensorflow/python/training/checkpoint_utils_test.py
index 4e08a1c859..1c1f126ce9 100644
--- a/tensorflow/python/training/checkpoint_utils_test.py
+++ b/tensorflow/python/training/checkpoint_utils_test.py
@@ -386,7 +386,9 @@ class CheckpointsTest(test.TestCase):
op for op in g.get_operations()
if (op.name.startswith("init_from_checkpoint/") and
not op.name.startswith("init_from_checkpoint/checkpoint_initializer"
- ) and op.type != "AssignVariableOp")
+ ) and
+ op.type != "AssignVariableOp" and
+ op.type != "Identity")
]
self.assertEqual(ops_in_init_from_checkpoint_scope, [])
diff --git a/tensorflow/python/training/checkpointable/base.py b/tensorflow/python/training/checkpointable/base.py
index ee35b01328..66837ee52f 100644
--- a/tensorflow/python/training/checkpointable/base.py
+++ b/tensorflow/python/training/checkpointable/base.py
@@ -144,7 +144,7 @@ class _CheckpointPosition(object):
# process deferred restorations for it and its dependencies.
restore_ops = checkpointable._restore_from_checkpoint_position(self) # pylint: disable=protected-access
if restore_ops:
- self._checkpoint.restore_ops.extend(restore_ops)
+ self._checkpoint.new_restore_ops(restore_ops)
def bind_object(self, checkpointable):
"""Set a checkpoint<->object correspondence and process slot variables.
@@ -501,12 +501,6 @@ class CheckpointableBase(object):
ValueError: If the variable name is not unique.
"""
self._maybe_initialize_checkpointable()
- if overwrite and self._lookup_dependency(name) is not None:
- raise ValueError(
- ("A variable named '%s' already exists in this Checkpointable, but "
- "Checkpointable._add_variable called to create another with "
- "that name. Variable names must be unique within a Checkpointable "
- "object.") % (name,))
with ops.init_scope():
if context.executing_eagerly():
# If this is a variable with a single Tensor stored in the checkpoint,
diff --git a/tensorflow/python/training/checkpointable/base_test.py b/tensorflow/python/training/checkpointable/base_test.py
index 950e9c5b53..fd935ac559 100644
--- a/tensorflow/python/training/checkpointable/base_test.py
+++ b/tensorflow/python/training/checkpointable/base_test.py
@@ -16,8 +16,11 @@ from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
+from tensorflow.python.framework import ops
+from tensorflow.python.ops import variable_scope
from tensorflow.python.platform import test
from tensorflow.python.training.checkpointable import base
+from tensorflow.python.training.checkpointable import util
class InterfaceTests(test.TestCase):
@@ -37,5 +40,22 @@ class InterfaceTests(test.TestCase):
self.assertIs(duplicate_name_dep, current_dependency)
self.assertEqual("leaf", current_name)
+ def testAddVariableOverwrite(self):
+ root = base.CheckpointableBase()
+ a = root._add_variable_with_custom_getter(
+ name="v", shape=[], getter=variable_scope.get_variable)
+ self.assertEqual([root, a], util.list_objects(root))
+ with ops.Graph().as_default():
+ b = root._add_variable_with_custom_getter(
+ name="v", shape=[], overwrite=True,
+ getter=variable_scope.get_variable)
+ self.assertEqual([root, b], util.list_objects(root))
+ with ops.Graph().as_default():
+ with self.assertRaisesRegexp(
+ ValueError, "already declared as a dependency"):
+ root._add_variable_with_custom_getter(
+ name="v", shape=[], overwrite=False,
+ getter=variable_scope.get_variable)
+
if __name__ == "__main__":
test.main()
diff --git a/tensorflow/python/training/checkpointable/tracking_test.py b/tensorflow/python/training/checkpointable/tracking_test.py
index f8d17cd417..e85f812ce2 100644
--- a/tensorflow/python/training/checkpointable/tracking_test.py
+++ b/tensorflow/python/training/checkpointable/tracking_test.py
@@ -165,7 +165,8 @@ class InterfaceTests(test.TestCase):
self.assertEqual([c], a.attribute["c"].layers)
checkpoint = util.Checkpoint(a=a)
save_path = checkpoint.save(os.path.join(self.get_temp_dir(), "ckpt"))
- checkpoint.restore(save_path).assert_consumed()
+ with self.test_session():
+ checkpoint.restore(save_path).assert_consumed().initialize_or_restore()
@test_util.run_in_graph_and_eager_modes
def testNoDepList(self):
diff --git a/tensorflow/python/training/checkpointable/util.py b/tensorflow/python/training/checkpointable/util.py
index 5d26a817d4..3cdaedce98 100644
--- a/tensorflow/python/training/checkpointable/util.py
+++ b/tensorflow/python/training/checkpointable/util.py
@@ -101,6 +101,7 @@ class _CheckpointRestoreCoordinator(object):
# this checkpoint.
self.restore_ops = []
self.restore_ops_by_name = {}
+ self.new_restore_ops_callback = None
# A mapping from optimizer proto ids to lists of slot variables to be
# restored when the optimizer is tracked. Only includes slot variables whose
# regular variables have already been created, and only for optimizer
@@ -121,6 +122,11 @@ class _CheckpointRestoreCoordinator(object):
slot_variable_id=slot_reference.slot_variable_node_id,
slot_name=slot_reference.slot_name))
+ def new_restore_ops(self, new_ops):
+ self.restore_ops.extend(new_ops)
+ if self.new_restore_ops_callback:
+ self.new_restore_ops_callback(new_ops) # pylint: disable=not-callable
+
class _NameBasedRestoreCoordinator(object):
"""Keeps the status of a name-based checkpoint restore."""
@@ -821,6 +827,31 @@ class _LoadStatus(object):
pass
+def streaming_restore(status, session=None):
+ """When graph building, runs restore ops as soon as they come in.
+
+ Args:
+ status: A _LoadStatus objects from an object-based saver's
+ restore(). Streaming restore from name-based checkpoints is not currently
+ supported.
+ session: A session to run new restore ops in.
+ """
+ if context.executing_eagerly():
+ # Streaming restore is the default/only behavior when executing eagerly.
+ return
+ if session is None:
+ session = ops.get_default_session()
+ if isinstance(status, NameBasedSaverStatus):
+ raise NotImplementedError(
+ "Streaming restore not supported from name-based checkpoints. File a "
+ "feature request if this limitation bothers you.")
+ status.run_restore_ops(session=session)
+ # pylint: disable=protected-access
+ status._checkpoint.new_restore_ops_callback = (
+ lambda ops: session.run(ops, feed_dict=status._feed_dict))
+ # pylint: enable=protected-access
+
+
class CheckpointLoadStatus(_LoadStatus):
"""Checks the status of checkpoint loading and manages restore ops.
@@ -912,7 +943,7 @@ class CheckpointLoadStatus(_LoadStatus):
if session is None:
session = ops.get_default_session()
all_objects = list_objects(self._root_checkpointable)
- already_initialized_objects = set(
+ already_initialized_objects = _ObjectIdentitySet(
self._checkpoint.object_by_proto_id.values())
initializers_for_non_restored_variables = [
c.initializer for c in all_objects
@@ -992,11 +1023,13 @@ _DEPRECATED_RESTORE_INSTRUCTIONS = (
"one this message is coming from) and use that checkpoint in the future.")
-@deprecation.deprecated(
- date=None, instructions=_DEPRECATED_RESTORE_INSTRUCTIONS)
class NameBasedSaverStatus(_LoadStatus):
"""Status for loading a name-based training checkpoint."""
+ # Ideally this deprecation decorator would be on the class, but that
+ # interferes with isinstance checks.
+ @deprecation.deprecated(
+ date=None, instructions=_DEPRECATED_RESTORE_INSTRUCTIONS)
def __init__(self, checkpoint, root_checkpointable):
self._checkpoint = checkpoint
self._root_checkpointable = root_checkpointable
diff --git a/tensorflow/python/training/saver.py b/tensorflow/python/training/saver.py
index 60885e9292..c80cdf03be 100644
--- a/tensorflow/python/training/saver.py
+++ b/tensorflow/python/training/saver.py
@@ -1925,6 +1925,14 @@ def import_meta_graph(meta_graph_or_file, clear_devices=False,
execution is enabled.
@end_compatibility
""" # pylint: disable=g-doc-exception
+ return _import_meta_graph_with_return_elements(
+ meta_graph_or_file, clear_devices, import_scope, **kwargs)[0]
+
+
+def _import_meta_graph_with_return_elements(
+ meta_graph_or_file, clear_devices=False, import_scope=None,
+ return_elements=None, **kwargs):
+ """Import MetaGraph, and return both a saver and returned elements."""
if context.executing_eagerly():
raise RuntimeError("Exporting/importing meta graphs is not supported when "
"eager execution is enabled. No graph exists when eager "
@@ -1934,12 +1942,22 @@ def import_meta_graph(meta_graph_or_file, clear_devices=False,
else:
meta_graph_def = meta_graph_or_file
- imported_vars = meta_graph.import_scoped_meta_graph(
- meta_graph_def,
- clear_devices=clear_devices,
- import_scope=import_scope,
- **kwargs)
+ imported_vars, imported_return_elements = (
+ meta_graph.import_scoped_meta_graph_with_return_elements(
+ meta_graph_def,
+ clear_devices=clear_devices,
+ import_scope=import_scope,
+ return_elements=return_elements,
+ **kwargs))
+
+ saver = _create_saver_from_imported_meta_graph(
+ meta_graph_def, import_scope, imported_vars)
+ return saver, imported_return_elements
+
+def _create_saver_from_imported_meta_graph(
+ meta_graph_def, import_scope, imported_vars):
+ """Return a saver for restoring variable values to an imported MetaGraph."""
if meta_graph_def.HasField("saver_def"):
# Infer the scope that is prepended by `import_scoped_meta_graph`.
scope = import_scope
diff --git a/tensorflow/python/training/saver_test.py b/tensorflow/python/training/saver_test.py
index ecce8ae6bd..204e81dda0 100644
--- a/tensorflow/python/training/saver_test.py
+++ b/tensorflow/python/training/saver_test.py
@@ -786,6 +786,37 @@ class SaverTest(test.TestCase):
self.assertEqual(20.0, v1.eval())
save.save(sess, save_path)
+ # Test restoring large tensors (triggers a thread pool)
+ def testRestoreLargeTensors(self):
+ save_dir = self.get_temp_dir()
+ def _model():
+ small_v = [variable_scope.get_variable(
+ "small%d" % i, shape=[10, 2], use_resource=True) for i in range(5)]
+ large_v = [variable_scope.get_variable(
+ "large%d" % i, shape=[32000, 1000], use_resource=True)
+ for i in range(3)]
+ return small_v + large_v
+
+ save_graph = ops_lib.Graph()
+ with save_graph.as_default(), self.test_session(graph=save_graph) as sess:
+ orig_vars = _model()
+ sess.run(variables.global_variables_initializer())
+ save = saver_module.Saver(max_to_keep=1)
+ variables.global_variables_initializer().run()
+ save.save(sess, save_dir)
+ orig_vals = sess.run(orig_vars)
+
+ restore_graph = ops_lib.Graph()
+ with restore_graph.as_default(), self.test_session(
+ graph=restore_graph) as sess:
+ restored_vars = _model()
+ save = saver_module.Saver(max_to_keep=1)
+ save.restore(sess, save_dir)
+ restored_vals = sess.run(restored_vars)
+
+ for orig, restored in zip(orig_vals, restored_vals):
+ self.assertAllEqual(orig, restored)
+
class SaveRestoreShardedTest(test.TestCase):
diff --git a/tensorflow/python/util/function_utils.py b/tensorflow/python/util/function_utils.py
index 7bbbde3cd2..4e9b07e20a 100644
--- a/tensorflow/python/util/function_utils.py
+++ b/tensorflow/python/util/function_utils.py
@@ -20,6 +20,8 @@ from __future__ import print_function
import functools
+import six
+
from tensorflow.python.util import tf_decorator
from tensorflow.python.util import tf_inspect
@@ -55,3 +57,36 @@ def fn_args(fn):
if _is_bounded_method(fn):
args.remove('self')
return tuple(args)
+
+
+def get_func_name(func):
+ """Returns name of passed callable."""
+ _, func = tf_decorator.unwrap(func)
+ if callable(func):
+ if tf_inspect.isfunction(func):
+ return func.__name__
+ elif tf_inspect.ismethod(func):
+ return '%s.%s' % (six.get_method_self(func).__class__.__name__,
+ six.get_method_function(func).__name__)
+ else: # Probably a class instance with __call__
+ return str(type(func))
+ else:
+ raise ValueError('Argument must be callable')
+
+
+def get_func_code(func):
+ """Returns func_code of passed callable, or None if not available."""
+ _, func = tf_decorator.unwrap(func)
+ if callable(func):
+ if tf_inspect.isfunction(func) or tf_inspect.ismethod(func):
+ return six.get_function_code(func)
+ # Since the object is not a function or method, but is a callable, we will
+ # try to access the __call__method as a function. This works with callable
+ # classes but fails with functool.partial objects despite their __call__
+ # attribute.
+ try:
+ return six.get_function_code(func.__call__)
+ except AttributeError:
+ return None
+ else:
+ raise ValueError('Argument must be callable')
diff --git a/tensorflow/python/util/function_utils_test.py b/tensorflow/python/util/function_utils_test.py
index e78cf6a5b0..1588328c26 100644
--- a/tensorflow/python/util/function_utils_test.py
+++ b/tensorflow/python/util/function_utils_test.py
@@ -24,6 +24,16 @@ from tensorflow.python.platform import test
from tensorflow.python.util import function_utils
+def silly_example_function():
+ pass
+
+
+class SillyCallableClass(object):
+
+ def __call__(self):
+ pass
+
+
class FnArgsTest(test.TestCase):
def test_simple_function(self):
@@ -124,5 +134,73 @@ class FnArgsTest(test.TestCase):
self.assertEqual(3, double_wrapped_fn(3))
self.assertEqual(3, double_wrapped_fn(a=3))
+
+class GetFuncNameTest(test.TestCase):
+
+ def testWithSimpleFunction(self):
+ self.assertEqual(
+ 'silly_example_function',
+ function_utils.get_func_name(silly_example_function))
+
+ def testWithClassMethod(self):
+ self.assertEqual(
+ 'GetFuncNameTest.testWithClassMethod',
+ function_utils.get_func_name(self.testWithClassMethod))
+
+ def testWithCallableClass(self):
+ callable_instance = SillyCallableClass()
+ self.assertRegexpMatches(
+ function_utils.get_func_name(callable_instance),
+ '<.*SillyCallableClass.*>')
+
+ def testWithFunctoolsPartial(self):
+ partial = functools.partial(silly_example_function)
+ self.assertRegexpMatches(
+ function_utils.get_func_name(partial),
+ '<.*functools.partial.*>')
+
+ def testWithLambda(self):
+ anon_fn = lambda x: x
+ self.assertEqual('<lambda>', function_utils.get_func_name(anon_fn))
+
+ def testRaisesWithNonCallableObject(self):
+ with self.assertRaises(ValueError):
+ function_utils.get_func_name(None)
+
+
+class GetFuncCodeTest(test.TestCase):
+
+ def testWithSimpleFunction(self):
+ code = function_utils.get_func_code(silly_example_function)
+ self.assertIsNotNone(code)
+ self.assertRegexpMatches(code.co_filename, 'function_utils_test.py')
+
+ def testWithClassMethod(self):
+ code = function_utils.get_func_code(self.testWithClassMethod)
+ self.assertIsNotNone(code)
+ self.assertRegexpMatches(code.co_filename, 'function_utils_test.py')
+
+ def testWithCallableClass(self):
+ callable_instance = SillyCallableClass()
+ code = function_utils.get_func_code(callable_instance)
+ self.assertIsNotNone(code)
+ self.assertRegexpMatches(code.co_filename, 'function_utils_test.py')
+
+ def testWithLambda(self):
+ anon_fn = lambda x: x
+ code = function_utils.get_func_code(anon_fn)
+ self.assertIsNotNone(code)
+ self.assertRegexpMatches(code.co_filename, 'function_utils_test.py')
+
+ def testWithFunctoolsPartial(self):
+ partial = functools.partial(silly_example_function)
+ code = function_utils.get_func_code(partial)
+ self.assertIsNone(code)
+
+ def testRaisesWithNonCallableObject(self):
+ with self.assertRaises(ValueError):
+ function_utils.get_func_code(None)
+
+
if __name__ == '__main__':
test.main()
diff --git a/tensorflow/python/util/nest.py b/tensorflow/python/util/nest.py
index 5aac559b9b..faae0d89c3 100644
--- a/tensorflow/python/util/nest.py
+++ b/tensorflow/python/util/nest.py
@@ -377,6 +377,62 @@ def map_structure(func, *structure, **check_types_dict):
structure[0], [func(*x) for x in entries])
+def map_structure_with_paths(func, *structure, **kwargs):
+ """Applies `func` to each entry in `structure` and returns a new structure.
+
+ Applies `func(path, x[0], x[1], ..., **kwargs)` where x[i] is an entry in
+ `structure[i]` and `path` is the common path to x[i] in the structures. All
+ structures in `structure` must have the same arity, and the return value will
+ contain the results in the same structure. Special kwarg `check_types`
+ determines whether the types of iterables within the structure must be the
+ same-- see **kwargs definition below.
+
+ Args:
+ func: A callable with the signature func(path, *values, **kwargs) that is
+ evaluated on the leaves of the structure.
+ *structure: A variable number of compatible structures to process.
+ **kwargs: Optional kwargs to be passed through to func. Special kwarg
+ `check_types` is not passed to func, but instead determines whether the
+ types of iterables within the structures have to be same (e.g.,
+ `map_structure(func, [1], (1,))` raises a `TypeError` exception). By
+ default, the types must match. To allow iteration over structures of
+ different types (but common arity), set this kwarg to `False`.
+
+ Returns:
+ A structure of the same form as the input structures whose leaves are the
+ result of evaluating func on corresponding leaves of the input structures.
+
+ Raises:
+ TypeError: If `func` is not callable or if the structures do not match
+ each other by depth tree.
+ TypeError: If `check_types` is not `False` and the two structures differ in
+ the type of sequence in any of their substructures.
+ ValueError: If no structures are provided.
+ """
+ if not callable(func):
+ raise TypeError("func must be callable, got: %s" % func)
+ if not structure:
+ raise ValueError("Must provide at least one structure")
+
+ check_types = kwargs.pop("check_types", True)
+ for other in structure[1:]:
+ assert_same_structure(structure[0], other, check_types=check_types)
+
+ # First set paths_and_values to:
+ # [[(p11, v11), ... (p1n, v1n)], ... [(pm1, vm1), ... (pmn, vmn)]]
+ paths_and_values = [flatten_with_joined_string_paths(s) for s in structure]
+
+ # Now zip(*paths_and_values) would be:
+ # [((p11, v11), ... (pm1, vm1)), ... ((p1n, v1n), ... (pmn, vmn))]
+ # so grouped_by_path is set to:
+ # [[(p11, ... pm1), (v11, ... vm1)], ... [(p1n, ... pmn), (v1n, ... vmn)]]
+ # Note that p1i, ... pmi must all be equal since the structures are the same.
+ grouped_by_path = [zip(*p_v) for p_v in zip(*paths_and_values)]
+
+ return pack_sequence_as(structure[0], [
+ func(paths[0], *values, **kwargs) for paths, values in grouped_by_path])
+
+
def _yield_flat_up_to(shallow_tree, input_tree):
"""Yields elements `input_tree` partially flattened up to `shallow_tree`."""
if is_sequence(shallow_tree):
diff --git a/tensorflow/python/util/nest_test.py b/tensorflow/python/util/nest_test.py
index 26c6ea4b01..fd75c6885a 100644
--- a/tensorflow/python/util/nest_test.py
+++ b/tensorflow/python/util/nest_test.py
@@ -746,6 +746,35 @@ class NestTest(parameterized.TestCase, test.TestCase):
self.assertEqual(
list(nest.flatten_with_joined_string_paths(inputs)), expected)
+ @parameterized.named_parameters(
+ ("tuples", (1, 2), (3, 4), True, (("0", 4), ("1", 6))),
+ ("dicts", {"a": 1, "b": 2}, {"b": 4, "a": 3}, True,
+ {"a": ("a", 4), "b": ("b", 6)}),
+ ("mixed", (1, 2), [3, 4], False, (("0", 4), ("1", 6))),
+ ("nested",
+ {"a": [2, 3], "b": [1, 2, 3]}, {"b": [5, 6, 7], "a": [8, 9]}, True,
+ {"a": [("a/0", 10), ("a/1", 12)],
+ "b": [("b/0", 6), ("b/1", 8), ("b/2", 10)]}))
+ def testMapWithPathsCompatibleStructures(self, s1, s2, check_types, expected):
+ def format_sum(path, *values):
+ return (path, sum(values))
+ result = nest.map_structure_with_paths(format_sum, s1, s2,
+ check_types=check_types)
+ self.assertEqual(expected, result)
+
+ @parameterized.named_parameters(
+ ("tuples", (1, 2), (3, 4, 5), ValueError),
+ ("dicts", {"a": 1}, {"b": 2}, ValueError),
+ ("mixed", (1, 2), [3, 4], TypeError),
+ ("nested",
+ {"a": [2, 3], "b": [1, 3]},
+ {"b": [5, 6, 7], "a": [8, 9]},
+ ValueError
+ ))
+ def testMapWithPathsIncompatibleStructures(self, s1, s2, error_type):
+ with self.assertRaises(error_type):
+ nest.map_structure_with_paths(lambda path, *s: 0, s1, s2)
+
class NestBenchmark(test.Benchmark):
diff --git a/tensorflow/stream_executor/cuda/cuda_blas.cc b/tensorflow/stream_executor/cuda/cuda_blas.cc
index 874bf0e8cb..6988389f29 100644
--- a/tensorflow/stream_executor/cuda/cuda_blas.cc
+++ b/tensorflow/stream_executor/cuda/cuda_blas.cc
@@ -643,7 +643,7 @@ bool CUDABlas::DoBlasInternalImpl(FuncT cublas_func, Stream *stream,
}
#endif
cublasStatus_t ret = cublas_func(parent_, blas_, args...);
- if (err_on_failure && ret != CUBLAS_STATUS_SUCCESS) {
+ if ((err_on_failure || VLOG_IS_ON(3)) && ret != CUBLAS_STATUS_SUCCESS) {
LOG(ERROR) << "failed to run cuBLAS routine " << cublas_func.kName << ": "
<< ToString(ret);
}
@@ -2139,6 +2139,10 @@ static bool UsesTensorOps(blas::AlgorithmType algo) {
template <typename InType>
static bool TensorOpsAvailable(int cc_major) {
#if CUDA_VERSION >= 9000
+ // cublas *does* allow tensor ops on inputs that are not fp16, so this is not
+ // strictly correct. We can't simply enable it, though, as that would change
+ // clients' behavior significantly: Using tensor ops on fp32 inputs cause them
+ // to be rounded to fp16.
if (cc_major >= 7 && TensorOpMathEnabled() &&
std::is_same<InType, Eigen::half>::value) {
return true;
@@ -2160,16 +2164,30 @@ bool CUDABlas::DoBlasGemmWithAlgorithmImpl(
if (stream->parent()->GetDeviceDescription().cuda_compute_capability(
&cc_major, &cc_minor) &&
cc_major < 5) {
+ VLOG(2) << "DoBlasGemmWithAlgorithm returning false because sm" << cc_major
+ << cc_minor << " devices don't support explicit gemm algorithms.";
return false;
}
if (UsesTensorOps(algorithm) && !TensorOpsAvailable<InT>(cc_major)) {
+ if (std::is_same<InT, Eigen::half>::value) {
+ VLOG(2) << "DoBlasGemmWithAlgorithm returning false because algorithm "
+ << algorithm
+ << " uses tensor ops, but tensor ops are not available in sm"
+ << cc_major << "X devices.";
+ } else {
+ VLOG(2) << "DoBlasGemmWithAlgorithm returning false because algorithm "
+ << algorithm
+ << " uses tensor ops, but the input data type is not fp16.";
+ }
return false;
}
// Either both 'alpha' and 'beta' need to be pointers to device memory, or
// they need to be both host scalars.
if (alpha.is_pointer() != beta.is_pointer()) {
+ VLOG(2) << "DoBlasGemmWithAlgorithm returning false because one of `alpha` "
+ "and `beta` is a pointer, but the other is not.";
return false;
}
@@ -2177,6 +2195,9 @@ bool CUDABlas::DoBlasGemmWithAlgorithmImpl(
if (output_profile_result != nullptr) {
timer.reset(new CUDATimer(parent_));
if (!timer->Init() || !timer->Start(AsCUDAStream(stream))) {
+ VLOG(2) << "DoBlasGemmWithAlgorithm returning false because "
+ "output_profile_result was given, but we were unable to "
+ "create a CUDATimer.";
return false;
}
}
@@ -2186,6 +2207,8 @@ bool CUDABlas::DoBlasGemmWithAlgorithmImpl(
#if CUDA_VERSION >= 9000 && CUDA_VERSION < 9020
if ((algorithm == CUBLAS_GEMM_DEFAULT || algorithm >= CUBLAS_GEMM_ALGO13) &&
std::max({m, n, k}) >= 2097153 && cc_major < 7) {
+ VLOG(2) << "DoBlasGemmWithAlgorithm returning false to work around cudnn "
+ "<9.2 bug with m, n, or k >= 2097153. See b/79126339.";
return false;
}
#endif
@@ -2211,6 +2234,8 @@ bool CUDABlas::DoBlasGemmWithAlgorithmImpl(
// CUDATimer will CHECK-fail if we Stop() it while the stream is in an error
// state.
if (!timer->Stop(AsCUDAStream(stream))) {
+ VLOG(2) << "DoBlasGemmWithAlgorithm returning false; unable to stop "
+ "CUDATimer.";
return false;
}
output_profile_result->set_is_valid(true);
@@ -2223,26 +2248,60 @@ bool CUDABlas::DoBlasGemmWithAlgorithmImpl(
bool CUDABlas::GetBlasGemmAlgorithms(
std::vector<blas::AlgorithmType> *out_algorithms) {
-// cublasGemmAlgo_t (and the function that accepts this type, cublasGemmEx)
-// were first introduced in CUDA 8.
-// Note that when CUDA version and compute capability is not sufficient, we
-// still return the out_algorithms. Caller needs to make sure that in this case,
-// the returned vector is empty.
- for (cublasGemmAlgo_t algo : {
- CUBLAS_GEMM_DFALT, CUBLAS_GEMM_ALGO0, CUBLAS_GEMM_ALGO1,
- CUBLAS_GEMM_ALGO2, CUBLAS_GEMM_ALGO3, CUBLAS_GEMM_ALGO4,
- CUBLAS_GEMM_ALGO5, CUBLAS_GEMM_ALGO6, CUBLAS_GEMM_ALGO7,
+ // cublasGemmAlgo_t (and the function that accepts this type, cublasGemmEx)
+ // were first introduced in CUDA 8.
+ //
+ // Note that when CUDA version and compute capability is not sufficient, we
+ // still return the out_algorithms. Caller needs to make sure that in this
+ // case, the returned vector is empty.
+ *out_algorithms = {
+ CUBLAS_GEMM_DFALT,
+ CUBLAS_GEMM_ALGO0,
+ CUBLAS_GEMM_ALGO1,
+ CUBLAS_GEMM_ALGO2,
+ CUBLAS_GEMM_ALGO3,
+ CUBLAS_GEMM_ALGO4,
+ CUBLAS_GEMM_ALGO5,
+ CUBLAS_GEMM_ALGO6,
+ CUBLAS_GEMM_ALGO7,
#if CUDA_VERSION >= 9000
- CUBLAS_GEMM_ALGO8, CUBLAS_GEMM_ALGO9, CUBLAS_GEMM_ALGO10,
- CUBLAS_GEMM_ALGO11, CUBLAS_GEMM_ALGO12, CUBLAS_GEMM_ALGO13,
- CUBLAS_GEMM_ALGO14, CUBLAS_GEMM_ALGO15, CUBLAS_GEMM_ALGO16,
- CUBLAS_GEMM_ALGO17, CUBLAS_GEMM_DFALT_TENSOR_OP,
- CUBLAS_GEMM_ALGO0_TENSOR_OP, CUBLAS_GEMM_ALGO1_TENSOR_OP,
- CUBLAS_GEMM_ALGO2_TENSOR_OP
+ CUBLAS_GEMM_ALGO8,
+ CUBLAS_GEMM_ALGO9,
+ CUBLAS_GEMM_ALGO10,
+ CUBLAS_GEMM_ALGO11,
+ CUBLAS_GEMM_ALGO12,
+ CUBLAS_GEMM_ALGO13,
+ CUBLAS_GEMM_ALGO14,
+ CUBLAS_GEMM_ALGO15,
+ CUBLAS_GEMM_ALGO16,
+ CUBLAS_GEMM_ALGO17,
+ CUBLAS_GEMM_DFALT_TENSOR_OP,
+ CUBLAS_GEMM_ALGO0_TENSOR_OP,
+ CUBLAS_GEMM_ALGO1_TENSOR_OP,
+ CUBLAS_GEMM_ALGO2_TENSOR_OP,
+ CUBLAS_GEMM_ALGO3_TENSOR_OP,
+ CUBLAS_GEMM_ALGO4_TENSOR_OP,
#endif
- }) {
- out_algorithms->push_back(algo);
- }
+#if CUDA_VERSION >= 9200
+ CUBLAS_GEMM_ALGO18,
+ CUBLAS_GEMM_ALGO19,
+ CUBLAS_GEMM_ALGO20,
+ CUBLAS_GEMM_ALGO21,
+ CUBLAS_GEMM_ALGO22,
+ CUBLAS_GEMM_ALGO23,
+ CUBLAS_GEMM_ALGO5_TENSOR_OP,
+ CUBLAS_GEMM_ALGO6_TENSOR_OP,
+ CUBLAS_GEMM_ALGO7_TENSOR_OP,
+ CUBLAS_GEMM_ALGO8_TENSOR_OP,
+ CUBLAS_GEMM_ALGO9_TENSOR_OP,
+ CUBLAS_GEMM_ALGO10_TENSOR_OP,
+ CUBLAS_GEMM_ALGO11_TENSOR_OP,
+ CUBLAS_GEMM_ALGO12_TENSOR_OP,
+ CUBLAS_GEMM_ALGO13_TENSOR_OP,
+ CUBLAS_GEMM_ALGO14_TENSOR_OP,
+ CUBLAS_GEMM_ALGO15_TENSOR_OP,
+#endif
+ };
return true;
}
diff --git a/tensorflow/stream_executor/cuda/cuda_dnn.cc b/tensorflow/stream_executor/cuda/cuda_dnn.cc
index 766a0dafb5..1c3940e92c 100644
--- a/tensorflow/stream_executor/cuda/cuda_dnn.cc
+++ b/tensorflow/stream_executor/cuda/cuda_dnn.cc
@@ -322,6 +322,7 @@ port::Status GetLoadedCudnnVersion(CudnnVersion* version) {
CudnnSupport::CudnnSupport(CUDAExecutor* parent) : parent_(parent) {}
port::Status CudnnSupport::Init() {
+ ScopedActivateExecutorContext context(parent_);
cudnnHandle_t cudnn_handle = nullptr;
auto status = cudnnCreate(&cudnn_handle);
if (status == CUDNN_STATUS_SUCCESS) {
diff --git a/tensorflow/stream_executor/cuda/cuda_driver.cc b/tensorflow/stream_executor/cuda/cuda_driver.cc
index d508f6594a..dbece3adf9 100644
--- a/tensorflow/stream_executor/cuda/cuda_driver.cc
+++ b/tensorflow/stream_executor/cuda/cuda_driver.cc
@@ -102,117 +102,16 @@ class CreatedContexts {
/* static */ int64 CreatedContexts::next_id_ = 1; // 0 means "no context"
// Formats CUresult to output prettified values into a log stream.
-// Error summaries taken from:
-// http://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__TYPES.html#group__CUDA__TYPES_1gc6c391505e117393cc2558fff6bfc2e9
-//
-// TODO(leary) switch to cuGetErrorName when updated cuda.h is available.
string ToString(CUresult result) {
-#define OSTREAM_CUDA_ERROR(__name) \
- case CUDA_ERROR_##__name: \
- return "CUDA_ERROR_" #__name;
-
-///////////////
-// NOTE: here we specify return code values outside of the enum explicitly
-// because our in-tree cuda.h is from the CUDA 5.5 SDK, but CUDA 6.0+ driver
-// libraries are deployed in the fleet these error codes are backwards
-// compatible, but if we see a "new" one, we want to be able to identify it in
-// the logs.
-//
-// Once we get a cuda.h that has cuGetErrorName (TODO is above) we can
-// eliminate this function and just rely on the driver to provide us these
-// strings.
-//
-// NOTE: "Must reboot all context" below is shorthand for, "must
-// destroy/recreate the offending context and any allocation which come from
-// it if you are to continue using CUDA."
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wswitch"
- switch (result) {
- OSTREAM_CUDA_ERROR(INVALID_VALUE)
- OSTREAM_CUDA_ERROR(OUT_OF_MEMORY)
- OSTREAM_CUDA_ERROR(NOT_INITIALIZED)
- OSTREAM_CUDA_ERROR(DEINITIALIZED)
- OSTREAM_CUDA_ERROR(NO_DEVICE)
- OSTREAM_CUDA_ERROR(INVALID_DEVICE)
- OSTREAM_CUDA_ERROR(INVALID_IMAGE)
- OSTREAM_CUDA_ERROR(INVALID_CONTEXT)
- OSTREAM_CUDA_ERROR(INVALID_HANDLE)
- OSTREAM_CUDA_ERROR(NOT_FOUND)
- OSTREAM_CUDA_ERROR(NOT_READY)
- OSTREAM_CUDA_ERROR(NO_BINARY_FOR_GPU)
-
- // Encountered an uncorrectable ECC error during execution.
- OSTREAM_CUDA_ERROR(ECC_UNCORRECTABLE)
-
- // Load/store on an invalid address. Must reboot all context.
- case 700:
- return "CUDA_ERROR_ILLEGAL_ADDRESS";
- // Passed too many / wrong arguments, too many threads for register count.
- case 701:
- return "CUDA_ERROR_LAUNCH_OUT_OF_RESOURCES";
- // Kernel took too long to execute.
- case 702:
- return "CUDA_ERROR_LAUNCH_TIMEOUT";
- // Kernel launch uses an incompatible texturing mode.
- case 703:
- return "CUDA_ERROR_LAUNCH_INCOMPATIBLE_TEXTURING";
- // Trying to re-enable peer access that already has it enabled.
- case 704:
- return "CUDA_ERROR_PEER_ACCESS_ALREADY_ENABLED";
- // Trying to disable peer access that has not yet been enabled.
- case 705:
- return "CUDA_ERROR_PEER_ACCESS_NOT_ENABLED";
- // Primary context for the specified device has already been initialized.
- case 708:
- return "CUDA_ERROR_PRIMARY_CONTEXT_ACTIVE";
- // Context current to calling thread has been destroyed or is a primary
- // context that has not yet been initialized.
- case 709:
- return "CUDA_ERROR_CONTEXT_IS_DESTROYED";
- // Device-side assert triggered during kernel execution. Must reboot all
- // context.
- case 710:
- return "CUDA_ERROR_ASSERT";
- // Hardware resources to enable peer access have been exhausted.
- case 711:
- return "CUDA_ERROR_TOO_MANY_PEERS";
- // Memory range has already been registered.
- case 712:
- return "CUDA_ERROR_HOST_MEMORY_ALREADY_REGISTERED";
- // Pointer does not correspond to any currently registered memory region.
- case 713:
- return "CUDA_ERROR_HOST_MEMORY_NOT_REGISTERED";
- // Due to stack corruption or exceeding stack size limit. Must reboot all
- // context.
- case 714:
- return "CUDA_ERROR_HARDWARE_STACK_ERROR";
- case 715:
- return "CUDA_ERROR_ILLEGAL_INSTRUCTION";
- // Load/store on an unaligned memory address. Must reboot all context.
- case 716:
- return "CUDA_ERROR_MISALIGNED_ADDRESS";
- // Device instruction with specific address space given address not
- // belonging to allowed address space. Must reboot all context.
- case 717:
- return "CUDA_ERROR_INVALID_ADDRESS_SPACE";
- // Device program counter wrapped its address space. Must reboot all
- // context.
- case 718:
- return "CUDA_ERROR_INVALID_PC";
- // Exception on device while executing a kernel; e.g. deref invalid device
- // pointer, accessing OOB shared memory. Must reboot all context.
- case 719:
- return "CUDA_ERROR_LAUNCH_FAILED";
-
- OSTREAM_CUDA_ERROR(CONTEXT_ALREADY_IN_USE)
- OSTREAM_CUDA_ERROR(PEER_ACCESS_UNSUPPORTED)
- OSTREAM_CUDA_ERROR(NOT_PERMITTED)
- OSTREAM_CUDA_ERROR(NOT_SUPPORTED)
- OSTREAM_CUDA_ERROR(UNKNOWN) // Unknown internal error to CUDA.
- default:
- return port::StrCat("CUresult(", static_cast<int>(result), ")");
+ const char *error_name;
+ if (cuGetErrorName(result, &error_name)) {
+ return port::StrCat("UNKNOWN ERROR (", static_cast<int>(result), ")");
+ }
+ const char *error_string;
+ if (cuGetErrorString(result, &error_string)) {
+ return error_name;
}
-#pragma GCC diagnostic pop
+ return port::StrCat(error_name, ": ", error_string);
}
// Returns the current context and checks that it is in the set of CUDA contexts
diff --git a/tensorflow/stream_executor/module_spec.h b/tensorflow/stream_executor/module_spec.h
index 212ae7ba9c..75bdfed2d7 100644
--- a/tensorflow/stream_executor/module_spec.h
+++ b/tensorflow/stream_executor/module_spec.h
@@ -43,6 +43,7 @@ class MultiModuleLoaderSpec {
}
void AddCudaCubinInMemory(port::ArraySlice<const uint8> cubin_bytes) {
+ CHECK(!cubin_bytes.empty());
has_cuda_cubin_in_memory_ = true;
cuda_cubin_in_memory_ = cubin_bytes;
}
diff --git a/tensorflow/stream_executor/stream.cc b/tensorflow/stream_executor/stream.cc
index 2c495c99e1..b0c061fd74 100644
--- a/tensorflow/stream_executor/stream.cc
+++ b/tensorflow/stream_executor/stream.cc
@@ -267,13 +267,13 @@ Stream::Stream(StreamExecutor *parent,
Stream::~Stream() {
VLOG_CALL();
- temporary_memory_manager_.ForceDeallocateAll();
// Ensure the stream is completed.
auto status = BlockHostUntilDone();
if (!status.ok()) {
LOG(WARNING) << "Error blocking host until done in stream destructor: "
<< status;
}
+ temporary_memory_manager_.ForceDeallocateAll();
if (allocated_) {
parent_->DeallocateStream(this);
@@ -1941,7 +1941,14 @@ void Stream::ReturnSubStream(Stream *sub_stream) {
mutex_lock lock(mu_);
for (auto &stream : sub_streams_) {
if (stream.first.get() == sub_stream) {
- stream.second = true;
+ // Streams have a monotonic state machine; if a stream
+ // encounters an error, it will remain in an error state
+ // forever. Only allow re-use of ok streams.
+ //
+ // TODO(toddw): Improve this mechanism, if necessary, to drop
+ // failed streams completely.
+ const bool ready_to_reuse = sub_stream->ok();
+ stream.second = ready_to_reuse;
return;
}
}
diff --git a/tensorflow/stream_executor/stream.h b/tensorflow/stream_executor/stream.h
index 63d64947c8..706442a666 100644
--- a/tensorflow/stream_executor/stream.h
+++ b/tensorflow/stream_executor/stream.h
@@ -125,7 +125,7 @@ class Stream {
Stream *GetOrCreateSubStream() LOCKS_EXCLUDED(mu_);
// Return the sub-stream back to the host stream so that it can be reused
- // later.
+ // later. Sub-streams that are !ok() will not be reused.
void ReturnSubStream(Stream *sub_stream) LOCKS_EXCLUDED(mu_);
// Allocate temporary memories. The stream will deallocate them when blocked
diff --git a/tensorflow/stream_executor/stream_test.cc b/tensorflow/stream_executor/stream_test.cc
new file mode 100644
index 0000000000..47dd675834
--- /dev/null
+++ b/tensorflow/stream_executor/stream_test.cc
@@ -0,0 +1,139 @@
+/* Copyright 2018 The TensorFlow Authors. All Rights Reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+==============================================================================*/
+
+#include "tensorflow/stream_executor/stream_executor.h"
+
+#include "tensorflow/core/platform/test.h"
+
+namespace stream_executor {
+namespace {
+
+class StreamTest : public ::testing::Test {
+ protected:
+ std::unique_ptr<StreamExecutor> NewStreamExecutor() {
+ Platform* platform =
+ MultiPlatformManager::PlatformWithName("Host").ConsumeValueOrDie();
+ StreamExecutorConfig config(/*ordinal=*/0);
+ return platform->GetUncachedExecutor(config).ConsumeValueOrDie();
+ }
+};
+
+TEST_F(StreamTest, NoInitNotOk) {
+ std::unique_ptr<StreamExecutor> executor = NewStreamExecutor();
+ Stream stream(executor.get());
+ EXPECT_FALSE(stream.ok());
+}
+
+TEST_F(StreamTest, InitOk) {
+ std::unique_ptr<StreamExecutor> executor = NewStreamExecutor();
+ Stream stream(executor.get());
+ stream.Init();
+ EXPECT_TRUE(stream.ok());
+}
+
+TEST_F(StreamTest, OneSubStream) {
+ std::unique_ptr<StreamExecutor> executor = NewStreamExecutor();
+ Stream stream(executor.get());
+ stream.Init();
+ EXPECT_TRUE(stream.ok());
+
+ // Get and return a sub-stream. Sub-streams are always initialized.
+ Stream* sub_stream1 = stream.GetOrCreateSubStream();
+ EXPECT_TRUE(sub_stream1->ok());
+ stream.ReturnSubStream(sub_stream1);
+
+ // Get and return another sub-stream.
+ Stream* sub_stream2 = stream.GetOrCreateSubStream();
+ EXPECT_TRUE(sub_stream2->ok());
+ stream.ReturnSubStream(sub_stream1);
+
+ // The underlying sub-streams should be the same, since sub_stream1
+ // was returned before we tried to get sub_stream2.
+ EXPECT_EQ(sub_stream1, sub_stream2);
+}
+
+TEST_F(StreamTest, TwoSubStreams) {
+ std::unique_ptr<StreamExecutor> executor = NewStreamExecutor();
+ Stream stream(executor.get());
+ stream.Init();
+ EXPECT_TRUE(stream.ok());
+
+ // Get two sub-streams.
+ Stream* sub_stream1 = stream.GetOrCreateSubStream();
+ EXPECT_TRUE(sub_stream1->ok());
+ Stream* sub_stream2 = stream.GetOrCreateSubStream();
+ EXPECT_TRUE(sub_stream2->ok());
+
+ // The underlying sub-streams should be different, since neither
+ // sub-stream has been returned.
+ EXPECT_NE(sub_stream1, sub_stream2);
+
+ // Return sub_stream1 and get sub_stream3, which should be the same.
+ stream.ReturnSubStream(sub_stream1);
+ Stream* sub_stream3 = stream.GetOrCreateSubStream();
+ EXPECT_TRUE(sub_stream3->ok());
+ EXPECT_EQ(sub_stream1, sub_stream3);
+ EXPECT_NE(sub_stream2, sub_stream3);
+
+ // Return sub_stream2 and get sub_stream4, which should be the same.
+ stream.ReturnSubStream(sub_stream2);
+ Stream* sub_stream4 = stream.GetOrCreateSubStream();
+ EXPECT_TRUE(sub_stream4->ok());
+ EXPECT_EQ(sub_stream2, sub_stream4);
+ EXPECT_NE(sub_stream3, sub_stream4);
+}
+
+TEST_F(StreamTest, FailedSubStreamNotReused) {
+ std::unique_ptr<StreamExecutor> executor = NewStreamExecutor();
+ Stream stream(executor.get());
+ stream.Init();
+ EXPECT_TRUE(stream.ok());
+
+ // Get a sub-stream.
+ Stream* sub_stream1 = stream.GetOrCreateSubStream();
+ EXPECT_TRUE(sub_stream1->ok());
+
+ // Force an error on the stream; here we call a method that requires
+ // DNN support, which we know the Host platform doesn't support.
+ sub_stream1->ThenDepthConcatenate({}, {}, nullptr);
+ EXPECT_FALSE(sub_stream1->ok());
+
+ // Return sub_stream1 and get sub_stream2.
+ stream.ReturnSubStream(sub_stream1);
+ Stream* sub_stream2 = stream.GetOrCreateSubStream();
+ EXPECT_TRUE(sub_stream2->ok());
+
+ // The underlying streams should be different. They would have been
+ // the same, but since we forced an error on sub_stream1, it will
+ // not be re-used. Sadly we can't just check:
+ // EXPECT_NE(sub_stream1, sub_stream2);
+ //
+ // The above should hold logically, but it may fail if the new
+ // stream instance allocated for sub_stream2 happens to reside in
+ // the same memory address as sub_stream1.
+ //
+ // The check that sub_stream2->ok() serves as a good-enough check.
+
+ // Return sub_stream2 and get sub_stream3. The previous error on
+ // sub_stream1 has no effect on these streams, and they are the
+ // same.
+ stream.ReturnSubStream(sub_stream2);
+ Stream* sub_stream3 = stream.GetOrCreateSubStream();
+ EXPECT_TRUE(sub_stream3->ok());
+ EXPECT_EQ(sub_stream2, sub_stream3);
+}
+
+} // namespace
+} // namespace stream_executor
diff --git a/tensorflow/tensorflow.bzl b/tensorflow/tensorflow.bzl
index 6f1a576c6c..45c8117304 100644
--- a/tensorflow/tensorflow.bzl
+++ b/tensorflow/tensorflow.bzl
@@ -9,6 +9,7 @@ load(
"tf_additional_grpc_deps_py",
"tf_additional_xla_deps_py",
"if_static",
+ "if_dynamic_kernels",
)
load(
"@local_config_tensorrt//:build_defs.bzl",
@@ -324,18 +325,36 @@ def tf_binary_additional_srcs():
clean_dep("//tensorflow:libtensorflow_framework.so"),
])
+
+# Helper functions to add kernel dependencies to tf binaries when using dynamic
+# kernel linking.
+def tf_binary_dynamic_kernel_dsos(kernels):
+ return if_dynamic_kernels(
+ extra_deps=["libtfkernel_%s.so" % clean_dep(k) for k in kernels],
+ otherwise=[])
+
+# Helper functions to add kernel dependencies to tf binaries when using static
+# kernel linking.
+def tf_binary_dynamic_kernel_deps(kernels):
+ return if_dynamic_kernels(
+ extra_deps=[],
+ otherwise=kernels)
+
def tf_cc_shared_object(
name,
srcs=[],
deps=[],
+ data=[],
linkopts=[],
framework_so=tf_binary_additional_srcs(),
+ kernels=[],
**kwargs):
native.cc_binary(
name=name,
srcs=srcs + framework_so,
- deps=deps,
+ deps=deps + tf_binary_dynamic_kernel_deps(kernels),
linkshared = 1,
+ data = data + tf_binary_dynamic_kernel_dsos(kernels),
linkopts=linkopts + _rpath_linkopts(name) + select({
clean_dep("//tensorflow:darwin"): [
"-Wl,-install_name,@rpath/" + name.split("/")[-1],
@@ -359,18 +378,21 @@ register_extension_info(
def tf_cc_binary(name,
srcs=[],
deps=[],
+ data=[],
linkopts=[],
copts=tf_copts(),
+ kernels=[],
**kwargs):
native.cc_binary(
name=name,
copts=copts,
srcs=srcs + tf_binary_additional_srcs(),
- deps=deps + if_mkl(
+ deps=deps + tf_binary_dynamic_kernel_deps(kernels) + if_mkl(
[
"//third_party/mkl:intel_binary_blob",
],
),
+ data=data + tf_binary_dynamic_kernel_dsos(kernels),
linkopts=linkopts + _rpath_linkopts(name),
**kwargs)
@@ -555,9 +577,6 @@ def tf_gen_op_wrappers_cc(name,
# is invalid to specify both "hidden" and "op_whitelist".
# cc_linkopts: Optional linkopts to be added to tf_cc_binary that contains the
# specified ops.
-# gen_locally: if True, the genrule to generate the Python library will be run
-# without sandboxing. This would help when the genrule depends on symlinks
-# which may not be supported in the sandbox.
def tf_gen_op_wrapper_py(name,
out=None,
hidden=None,
@@ -568,8 +587,7 @@ def tf_gen_op_wrapper_py(name,
generated_target_name=None,
op_whitelist=[],
cc_linkopts=[],
- api_def_srcs=[],
- gen_locally=False):
+ api_def_srcs=[]):
if (hidden or hidden_file) and op_whitelist:
fail('Cannot pass specify both hidden and op_whitelist.')
@@ -624,7 +642,6 @@ def tf_gen_op_wrapper_py(name,
outs=[out],
srcs=api_def_srcs + [hidden_file],
tools=[tool_name] + tf_binary_additional_srcs(),
- local = (1 if gen_locally else 0),
cmd=("$(location " + tool_name + ") " + api_def_args_str +
" @$(location " + hidden_file + ") " +
("1" if require_shape_functions else "0") + " > $@"))
@@ -634,7 +651,6 @@ def tf_gen_op_wrapper_py(name,
outs=[out],
srcs=api_def_srcs,
tools=[tool_name] + tf_binary_additional_srcs(),
- local = (1 if gen_locally else 0),
cmd=("$(location " + tool_name + ") " + api_def_args_str + " " +
op_list_arg + " " +
("1" if require_shape_functions else "0") + " " +
@@ -664,11 +680,13 @@ def tf_gen_op_wrapper_py(name,
def tf_cc_test(name,
srcs,
deps,
+ data=[],
linkstatic=0,
extra_copts=[],
suffix="",
linkopts=[],
nocopts=None,
+ kernels=[],
**kwargs):
native.cc_test(
name="%s%s" % (name, suffix),
@@ -688,11 +706,12 @@ def tf_cc_test(name,
"-lm"
],
}) + linkopts + _rpath_linkopts(name),
- deps=deps + if_mkl(
+ deps=deps + tf_binary_dynamic_kernel_deps(kernels) + if_mkl(
[
"//third_party/mkl:intel_binary_blob",
],
),
+ data=data + tf_binary_dynamic_kernel_dsos(kernels),
# Nested select() statements seem not to be supported when passed to
# linkstatic, and we already have a cuda select() passed in to this
# function.
@@ -793,6 +812,7 @@ def tf_cuda_only_cc_test(name,
size="medium",
linkstatic=0,
args=[],
+ kernels=[],
linkopts=[]):
native.cc_test(
name="%s%s" % (name, "_gpu"),
@@ -800,8 +820,8 @@ def tf_cuda_only_cc_test(name,
size=size,
args=args,
copts= _cuda_copts() + tf_copts(),
- data=data,
- deps=deps + if_cuda([
+ data=data + tf_binary_dynamic_kernel_dsos(kernels),
+ deps=deps + tf_binary_dynamic_kernel_deps(kernels) + if_cuda([
clean_dep("//tensorflow/core:cuda"),
clean_dep("//tensorflow/core:gpu_lib")]),
linkopts=if_not_windows(["-lpthread", "-lm"]) + linkopts + _rpath_linkopts(name),
@@ -844,9 +864,11 @@ def tf_cc_tests(srcs,
def tf_cc_test_mkl(srcs,
deps,
name="",
+ data=[],
linkstatic=0,
tags=[],
size="medium",
+ kernels=[],
args=None):
# -fno-exceptions in nocopts breaks compilation if header modules are enabled.
disable_header_modules = ["-use_header_modules"]
@@ -867,11 +889,12 @@ def tf_cc_test_mkl(srcs,
"-lm"
],
}) + _rpath_linkopts(src_to_test_name(src)),
- deps=deps + if_mkl(
+ deps=deps + tf_binary_dynamic_kernel_deps(kernels) + if_mkl(
[
"//third_party/mkl:intel_binary_blob",
],
),
+ data=data + tf_binary_dynamic_kernel_dsos(kernels),
linkstatic=linkstatic,
tags=tags,
size=size,
@@ -911,12 +934,13 @@ def tf_cuda_cc_tests(srcs,
def tf_java_test(name,
srcs=[],
deps=[],
+ kernels=[],
*args,
**kwargs):
native.java_test(
name=name,
srcs=srcs,
- deps=deps + tf_binary_additional_srcs(),
+ deps=deps + tf_binary_additional_srcs() + tf_binary_dynamic_kernel_dsos(kernels) + tf_binary_dynamic_kernel_deps(kernels),
*args,
**kwargs)
@@ -1090,6 +1114,15 @@ def tf_kernel_library(
deps=deps,
**kwargs)
+ # TODO(gunan): CUDA dependency not clear here. Fix it.
+ tf_cc_shared_object(
+ name="libtfkernel_%s.so" % name,
+ srcs=srcs + hdrs,
+ copts=copts,
+ deps=deps,
+ tags=["manual", "notap"])
+
+
register_extension_info(
extension_name = "tf_kernel_library",
label_regex_for_dep = "{extension_name}(_gpu)?",
@@ -1174,7 +1207,6 @@ _py_wrap_cc = rule(
allow_files = True,
),
"swig_includes": attr.label_list(
- cfg = "data",
allow_files = True,
),
"deps": attr.label_list(
@@ -1462,7 +1494,7 @@ def tf_py_wrap_cc(name,
srcs=srcs,
swig_includes=swig_includes,
deps=deps + extra_deps,
- toolchain_deps=["//tools/defaults:crosstool"],
+ toolchain_deps=["@bazel_tools//tools/cpp:current_cc_toolchain"],
module_name=module_name,
py_module_name=name)
vscriptname=name+"_versionscript"
diff --git a/tensorflow/tools/api/golden/tensorflow.-config-proto.-experimental.pbtxt b/tensorflow/tools/api/golden/tensorflow.-config-proto.-experimental.pbtxt
index ef9fe096a1..eb41deee13 100644
--- a/tensorflow/tools/api/golden/tensorflow.-config-proto.-experimental.pbtxt
+++ b/tensorflow/tools/api/golden/tensorflow.-config-proto.-experimental.pbtxt
@@ -14,5 +14,11 @@ tf_proto {
label: LABEL_OPTIONAL
type: TYPE_BOOL
}
+ field {
+ name: "executor_type"
+ number: 3
+ label: LABEL_OPTIONAL
+ type: TYPE_STRING
+ }
}
}
diff --git a/tensorflow/tools/api/golden/tensorflow.-config-proto.pbtxt b/tensorflow/tools/api/golden/tensorflow.-config-proto.pbtxt
index eeef15515d..e565b903d2 100644
--- a/tensorflow/tools/api/golden/tensorflow.-config-proto.pbtxt
+++ b/tensorflow/tools/api/golden/tensorflow.-config-proto.pbtxt
@@ -137,6 +137,12 @@ tf_proto {
label: LABEL_OPTIONAL
type: TYPE_BOOL
}
+ field {
+ name: "executor_type"
+ number: 3
+ label: LABEL_OPTIONAL
+ type: TYPE_STRING
+ }
}
}
}
diff --git a/tensorflow/tools/api/golden/tensorflow.estimator.-boosted-trees-classifier.pbtxt b/tensorflow/tools/api/golden/tensorflow.estimator.-boosted-trees-classifier.pbtxt
index 9dbb5d16a4..c23b04b4ef 100644
--- a/tensorflow/tools/api/golden/tensorflow.estimator.-boosted-trees-classifier.pbtxt
+++ b/tensorflow/tools/api/golden/tensorflow.estimator.-boosted-trees-classifier.pbtxt
@@ -21,7 +21,7 @@ tf_class {
}
member_method {
name: "__init__"
- argspec: "args=[\'self\', \'feature_columns\', \'n_batches_per_layer\', \'model_dir\', \'n_classes\', \'weight_column\', \'label_vocabulary\', \'n_trees\', \'max_depth\', \'learning_rate\', \'l1_regularization\', \'l2_regularization\', \'tree_complexity\', \'min_node_weight\', \'config\', \'center_bias\'], varargs=None, keywords=None, defaults=[\'None\', \'<object object instance>\', \'None\', \'None\', \'100\', \'6\', \'0.1\', \'0.0\', \'0.0\', \'0.0\', \'0.0\', \'None\', \'False\'], "
+ argspec: "args=[\'self\', \'feature_columns\', \'n_batches_per_layer\', \'model_dir\', \'n_classes\', \'weight_column\', \'label_vocabulary\', \'n_trees\', \'max_depth\', \'learning_rate\', \'l1_regularization\', \'l2_regularization\', \'tree_complexity\', \'min_node_weight\', \'config\', \'center_bias\', \'pruning_mode\'], varargs=None, keywords=None, defaults=[\'None\', \'<object object instance>\', \'None\', \'None\', \'100\', \'6\', \'0.1\', \'0.0\', \'0.0\', \'0.0\', \'0.0\', \'None\', \'False\', \'none\'], "
}
member_method {
name: "eval_dir"
diff --git a/tensorflow/tools/api/golden/tensorflow.estimator.-boosted-trees-regressor.pbtxt b/tensorflow/tools/api/golden/tensorflow.estimator.-boosted-trees-regressor.pbtxt
index 34a30c2874..6878d28fff 100644
--- a/tensorflow/tools/api/golden/tensorflow.estimator.-boosted-trees-regressor.pbtxt
+++ b/tensorflow/tools/api/golden/tensorflow.estimator.-boosted-trees-regressor.pbtxt
@@ -21,7 +21,7 @@ tf_class {
}
member_method {
name: "__init__"
- argspec: "args=[\'self\', \'feature_columns\', \'n_batches_per_layer\', \'model_dir\', \'label_dimension\', \'weight_column\', \'n_trees\', \'max_depth\', \'learning_rate\', \'l1_regularization\', \'l2_regularization\', \'tree_complexity\', \'min_node_weight\', \'config\', \'center_bias\'], varargs=None, keywords=None, defaults=[\'None\', \'<object object instance>\', \'None\', \'100\', \'6\', \'0.1\', \'0.0\', \'0.0\', \'0.0\', \'0.0\', \'None\', \'False\'], "
+ argspec: "args=[\'self\', \'feature_columns\', \'n_batches_per_layer\', \'model_dir\', \'label_dimension\', \'weight_column\', \'n_trees\', \'max_depth\', \'learning_rate\', \'l1_regularization\', \'l2_regularization\', \'tree_complexity\', \'min_node_weight\', \'config\', \'center_bias\', \'pruning_mode\'], varargs=None, keywords=None, defaults=[\'None\', \'<object object instance>\', \'None\', \'100\', \'6\', \'0.1\', \'0.0\', \'0.0\', \'0.0\', \'0.0\', \'None\', \'False\', \'none\'], "
}
member_method {
name: "eval_dir"
diff --git a/tensorflow/tools/api/golden/tensorflow.estimator.-run-config.pbtxt b/tensorflow/tools/api/golden/tensorflow.estimator.-run-config.pbtxt
index 5aa4b3d4fb..bf1f94b6ae 100644
--- a/tensorflow/tools/api/golden/tensorflow.estimator.-run-config.pbtxt
+++ b/tensorflow/tools/api/golden/tensorflow.estimator.-run-config.pbtxt
@@ -11,6 +11,10 @@ tf_class {
mtype: "<type \'property\'>"
}
member {
+ name: "eval_distribute"
+ mtype: "<type \'property\'>"
+ }
+ member {
name: "evaluation_master"
mtype: "<type \'property\'>"
}
@@ -92,7 +96,7 @@ tf_class {
}
member_method {
name: "__init__"
- argspec: "args=[\'self\', \'model_dir\', \'tf_random_seed\', \'save_summary_steps\', \'save_checkpoints_steps\', \'save_checkpoints_secs\', \'session_config\', \'keep_checkpoint_max\', \'keep_checkpoint_every_n_hours\', \'log_step_count_steps\', \'train_distribute\', \'device_fn\', \'protocol\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'100\', \'<object object instance>\', \'<object object instance>\', \'None\', \'5\', \'10000\', \'100\', \'None\', \'None\', \'None\'], "
+ argspec: "args=[\'self\', \'model_dir\', \'tf_random_seed\', \'save_summary_steps\', \'save_checkpoints_steps\', \'save_checkpoints_secs\', \'session_config\', \'keep_checkpoint_max\', \'keep_checkpoint_every_n_hours\', \'log_step_count_steps\', \'train_distribute\', \'device_fn\', \'protocol\', \'eval_distribute\'], varargs=None, keywords=None, defaults=[\'None\', \'None\', \'100\', \'<object object instance>\', \'<object object instance>\', \'None\', \'5\', \'10000\', \'100\', \'None\', \'None\', \'None\', \'None\'], "
}
member_method {
name: "replace"
diff --git a/tensorflow/tools/api/golden/tensorflow.image.pbtxt b/tensorflow/tools/api/golden/tensorflow.image.pbtxt
index 6ec3aba775..5c46dc5ee7 100644
--- a/tensorflow/tools/api/golden/tensorflow.image.pbtxt
+++ b/tensorflow/tools/api/golden/tensorflow.image.pbtxt
@@ -125,6 +125,10 @@ tf_module {
argspec: "args=[\'overlaps\', \'scores\', \'max_output_size\', \'overlap_threshold\', \'score_threshold\', \'name\'], varargs=None, keywords=None, defaults=[\'0.5\', \'-inf\', \'None\'], "
}
member_method {
+ name: "non_max_suppression_padded"
+ argspec: "args=[\'boxes\', \'scores\', \'max_output_size\', \'iou_threshold\', \'score_threshold\', \'pad_to_max_output_size\', \'name\'], varargs=None, keywords=None, defaults=[\'0.5\', \'-inf\', \'False\', \'None\'], "
+ }
+ member_method {
name: "pad_to_bounding_box"
argspec: "args=[\'image\', \'offset_height\', \'offset_width\', \'target_height\', \'target_width\'], varargs=None, keywords=None, defaults=None"
}
diff --git a/tensorflow/tools/ci_build/Dockerfile.gpu b/tensorflow/tools/ci_build/Dockerfile.gpu
index 33d0425918..383f9545c9 100644
--- a/tensorflow/tools/ci_build/Dockerfile.gpu
+++ b/tensorflow/tools/ci_build/Dockerfile.gpu
@@ -15,10 +15,6 @@ RUN add-apt-repository -y ppa:openjdk-r/ppa && \
add-apt-repository -y ppa:george-edison55/cmake-3.x
RUN /install/install_deb_packages.sh
-# Install NCCL
-RUN apt-get update && apt-get install -y --no-install-recommends \
- libnccl2=2.2.13-1+cuda9.0
-
RUN /install/install_pip_packages.sh
RUN /install/install_bazel.sh
RUN /install/install_golang.sh
diff --git a/tensorflow/tools/ci_build/ci_build.sh b/tensorflow/tools/ci_build/ci_build.sh
index f6a50d3d4c..77265e0f50 100755
--- a/tensorflow/tools/ci_build/ci_build.sh
+++ b/tensorflow/tools/ci_build/ci_build.sh
@@ -115,6 +115,7 @@ DOCKER_IMG_NAME=$(echo "${DOCKER_IMG_NAME}" | tr '[:upper:]' '[:lower:]')
# Print arguments.
echo "WORKSPACE: ${WORKSPACE}"
+echo "CI_DOCKER_BUILD_EXTRA_PARAMS: ${CI_DOCKER_BUILD_EXTRA_PARAMS[*]}"
echo "CI_DOCKER_EXTRA_PARAMS: ${CI_DOCKER_EXTRA_PARAMS[*]}"
echo "COMMAND: ${COMMAND[*]}"
echo "CI_COMMAND_PREFIX: ${CI_COMMAND_PREFIX[*]}"
@@ -126,7 +127,7 @@ echo ""
# Build the docker container.
echo "Building container (${DOCKER_IMG_NAME})..."
-docker build -t ${DOCKER_IMG_NAME} \
+docker build -t ${DOCKER_IMG_NAME} ${CI_DOCKER_BUILD_EXTRA_PARAMS[@]} \
-f "${DOCKERFILE_PATH}" "${DOCKER_CONTEXT_PATH}"
# Check docker build status
diff --git a/tensorflow/tools/ci_build/ci_parameterized_build.sh b/tensorflow/tools/ci_build/ci_parameterized_build.sh
index 5115be8c6d..993894d658 100755
--- a/tensorflow/tools/ci_build/ci_parameterized_build.sh
+++ b/tensorflow/tools/ci_build/ci_parameterized_build.sh
@@ -541,33 +541,35 @@ echo ""
TMP_DIR=""
DOCKERFILE_FLAG=""
-if [[ "${TF_BUILD_PYTHON_VERSION}" == "python3.5" ]] ||
- [[ "${TF_BUILD_PYTHON_VERSION}" == "python3.6" ]]; then
- # Modify Dockerfile for Python3.5 | Python3.6 build
- TMP_DIR=$(mktemp -d)
- echo "Docker build will occur in temporary directory: ${TMP_DIR}"
-
- # Copy the files required for the docker build
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
- cp -r "${SCRIPT_DIR}/install" "${TMP_DIR}/install" || \
- die "ERROR: Failed to copy directory ${SCRIPT_DIR}/install"
-
- DOCKERFILE="${SCRIPT_DIR}/Dockerfile.${TF_BUILD_CONTAINER_TYPE}"
- cp "${DOCKERFILE}" "${TMP_DIR}/" || \
- die "ERROR: Failed to copy Dockerfile at ${DOCKERFILE}"
- DOCKERFILE="${TMP_DIR}/Dockerfile.${TF_BUILD_CONTAINER_TYPE}"
-
- # Replace a line in the Dockerfile
- if sed -i \
- "s/RUN \/install\/install_pip_packages.sh/RUN \/install\/install_${TF_BUILD_PYTHON_VERSION}_pip_packages.sh/g" \
- "${DOCKERFILE}"
- then
- echo "Copied and modified Dockerfile for ${TF_BUILD_PYTHON_VERSION} build: ${DOCKERFILE}"
- else
- die "ERROR: Faild to copy and modify Dockerfile: ${DOCKERFILE}"
- fi
+if [[ "${DO_DOCKER}" == "1" ]]; then
+ if [[ "${TF_BUILD_PYTHON_VERSION}" == "python3.5" ]] ||
+ [[ "${TF_BUILD_PYTHON_VERSION}" == "python3.6" ]]; then
+ # Modify Dockerfile for Python3.5 | Python3.6 build
+ TMP_DIR=$(mktemp -d)
+ echo "Docker build will occur in temporary directory: ${TMP_DIR}"
+
+ # Copy the files required for the docker build
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+ cp -r "${SCRIPT_DIR}/install" "${TMP_DIR}/install" || \
+ die "ERROR: Failed to copy directory ${SCRIPT_DIR}/install"
+
+ DOCKERFILE="${SCRIPT_DIR}/Dockerfile.${TF_BUILD_CONTAINER_TYPE}"
+ cp "${DOCKERFILE}" "${TMP_DIR}/" || \
+ die "ERROR: Failed to copy Dockerfile at ${DOCKERFILE}"
+ DOCKERFILE="${TMP_DIR}/Dockerfile.${TF_BUILD_CONTAINER_TYPE}"
+
+ # Replace a line in the Dockerfile
+ if sed -i \
+ "s/RUN \/install\/install_pip_packages.sh/RUN \/install\/install_${TF_BUILD_PYTHON_VERSION}_pip_packages.sh/g" \
+ "${DOCKERFILE}"
+ then
+ echo "Copied and modified Dockerfile for ${TF_BUILD_PYTHON_VERSION} build: ${DOCKERFILE}"
+ else
+ die "ERROR: Faild to copy and modify Dockerfile: ${DOCKERFILE}"
+ fi
- DOCKERFILE_FLAG="--dockerfile ${DOCKERFILE}"
+ DOCKERFILE_FLAG="--dockerfile ${DOCKERFILE}"
+ fi
fi
chmod +x ${TMP_SCRIPT}
diff --git a/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl b/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl
deleted file mode 100644
index 6796ad70e5..0000000000
--- a/tensorflow/tools/docker/Dockerfile.devel-cpu-mkl
+++ /dev/null
@@ -1,83 +0,0 @@
-FROM tensorflow/tensorflow:latest-devel
-
-LABEL maintainer="Clayne Robison<clayne.b.robison@intel.com>"
-
-# These arguments are parameterized. Use --build-args to override.
-ARG TF_BRANCH=r1.9
-ARG WHL_DIR=/whl
-
-RUN apt-get update && apt-get install -y --no-install-recommends \
- golang \
- vim \
- emacs \
- && \
- apt-get clean && \
- rm -rf /var/lib/apt/lists/*
-
-RUN pip --no-cache-dir install --upgrade \
- pip setuptools
-
-RUN pip --no-cache-dir install wheel
-
-# Download and build TensorFlow.
-WORKDIR /
-RUN rm -rf tensorflow && \
- git clone https://github.com/tensorflow/tensorflow.git && \
- cd tensorflow && \
- git checkout ${TF_BRANCH}
-WORKDIR /tensorflow
-
-# Configure the build for CPU with MKL by accepting default build options and
-# setting library locations
-ENV CI_BUILD_PYTHON=python \
- LD_LIBRARY_PATH=${LD_LIBRARY_PATH} \
- PYTHON_BIN_PATH=/usr/bin/python \
- PYTHON_LIB_PATH=/usr/local/lib/python2.7/dist-packages \
- CC_OPT_FLAGS='-march=native' \
- TF_NEED_JEMALLOC=0 \
- TF_NEED_GCP=1 \
- TF_NEED_CUDA=0 \
- TF_NEED_HDFS=0 \
- TF_NEED_S3=1 \
- TF_NEED_OPENCL=0 \
- TF_NEED_GDR=0 \
- TF_ENABLE_XLA=0 \
- TF_NEED_VERBS=0 \
- TF_NEED_MPI=0
-RUN ./configure
-
-# Build and Install TensorFlow.
-# The 'mkl' option builds with Intel(R) Math Kernel Library (MKL), which detects
-# the platform it is currently running on and takes appropriately optimized
-# paths. The -march=native option is for code that is not in MKL, and assumes
-# this container will be run on the same architecture on which it is built.
-RUN LD_LIBRARY_PATH=${LD_LIBRARY_PATH} \
- bazel build --config=mkl \
- --config="opt" \
- --copt="-march=broadwell" \
- --copt="-O3" \
- //tensorflow/tools/pip_package:build_pip_package && \
- mkdir ${WHL_DIR} && \
- bazel-bin/tensorflow/tools/pip_package/build_pip_package ${WHL_DIR}
-
-# Clean up Bazel cache when done, but leave the whl.
-# This will upgrade the default Tensorflow version with the Intel MKL version
-RUN pip --no-cache-dir install --upgrade ${WHL_DIR}/tensorflow-*.whl && \
- rm -rf /root/.cache
-
-WORKDIR /root
-
-#add welcome message with instructions
-
-RUN echo '[ ! -z "$TERM" -a -r /etc/motd ] && cat /etc/issue && cat /etc/motd' \
- >> /etc/bash.bashrc \
- ; echo "\
-||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||\n\
-| \n\
-| Docker container running Ubuntu \n\
-| with TensorFlow ${TF_BRANCH} optimized for CPU \n\
-| with Intel(R) MKL \n\
-| \n\
-||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||\n\
-\n "\
- > /etc/motd
diff --git a/tensorflow/tools/pip_package/BUILD b/tensorflow/tools/pip_package/BUILD
index ab39ed8d69..fdc2862a12 100644
--- a/tensorflow/tools/pip_package/BUILD
+++ b/tensorflow/tools/pip_package/BUILD
@@ -63,6 +63,7 @@ COMMON_PIP_DEPS = [
"//tensorflow/contrib/autograph/lang:lang",
"//tensorflow/contrib/autograph/operators:operators",
"//tensorflow/contrib/autograph/pyct:pyct",
+ "//tensorflow/contrib/autograph/pyct/testing:testing",
"//tensorflow/contrib/autograph/pyct/static_analysis:static_analysis",
"//tensorflow/contrib/autograph/pyct/common_transformers:common_transformers",
"//tensorflow/contrib/boosted_trees:boosted_trees_pip",
diff --git a/tensorflow/workspace.bzl b/tensorflow/workspace.bzl
index fad96d11bf..c145cfe72e 100644
--- a/tensorflow/workspace.bzl
+++ b/tensorflow/workspace.bzl
@@ -485,11 +485,11 @@ def tf_workspace(path_prefix="", tf_repo_name=""):
tf_http_archive(
name = "llvm",
urls = [
- "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/35ffbe6bcf3b755f30633d834534a892b4c5fb29.tar.gz",
- "https://github.com/llvm-mirror/llvm/archive/35ffbe6bcf3b755f30633d834534a892b4c5fb29.tar.gz",
+ "https://mirror.bazel.build/github.com/llvm-mirror/llvm/archive/7b3bfc8151f3a6bcd9642c49c1f86f66cc43a428.tar.gz",
+ "https://github.com/llvm-mirror/llvm/archive/7b3bfc8151f3a6bcd9642c49c1f86f66cc43a428.tar.gz",
],
- sha256 = "42b3924b56339bb953b587f3e55788c8fefa51068756e6ac2ee4aed9c187bbb8",
- strip_prefix = "llvm-35ffbe6bcf3b755f30633d834534a892b4c5fb29",
+ sha256 = "c6cbb21acd46e3e00faa8c379595ecffb99ef77622da17f29371db2bfad1d3d3",
+ strip_prefix = "llvm-7b3bfc8151f3a6bcd9642c49c1f86f66cc43a428",
build_file = clean_dep("//third_party/llvm:llvm.autogenerated.BUILD"),
)